flutter delete item from listview
Asked Answered
P

4

11

I tried to do the

Write Your First Flutter App, part 2 flutter app page 5

I now have a question for this application. I want to remove an entry from that List onLongPress like this:

 onLongPress: () {
              setState(() {
                _saved.remove(pair);
              });
            },

This will remove the item from the list but won't update the screen. On returning to the home and reopening this route the new items without the deleted. But how can I trigger the update on this page without the user to reopen the page.

 import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: RandomWords(),
      theme: new ThemeData(
        primaryColor: Colors.orange,
      ),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  RandomWordsState createState() => RandomWordsState();
}

class RandomWordsState extends State<RandomWords> {
  final List<WordPair> _suggestions = <WordPair>[];
  final TextStyle _biggerFont = const TextStyle(fontSize: 18);
  final Set<WordPair> _saved = new Set<WordPair>();

  _buildSuggestions() {
    return ListView.builder(
      padding: const EdgeInsets.all(16),
      itemBuilder: (context, i) {
        if (i.isOdd) return Divider();

        final index = i ~/ 2;
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      },
    );
  }

  Widget _buildRow(WordPair pair) {
    final bool alreadySaved = _saved.contains(pair);

    return ListTile(
      title: Text(pair.asPascalCase, style: _biggerFont),
      trailing: new Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
      onTap: () {
        setState(() {
          if (alreadySaved) {
            _saved.remove(pair);
          } else {
            _saved.add(pair);
          }
        });
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Startup Name Generator"),
        actions: <Widget>[
          new IconButton(icon: const Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }

  void _pushSaved() {
    Navigator.of(context).push(
      new MaterialPageRoute(
        builder: (BuildContext context) {
          final Iterable<ListTile> tiles = _saved.map(
            (WordPair pair) {
              return new ListTile(
//this is the delete operation
                onLongPress: () {
                  setState(() {
                    _saved.remove(pair);
                  });
                },
                title: new Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );

          final List<Widget> divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();

          return new Scaffold(
            appBar: new AppBar(
              title: const Text('Saved Suggestions'),
            ),
            body: new ListView(children: divided),
          );
        },
      ),
    );
  }
}
Psychologist answered 13/3, 2019 at 13:25 Comment(0)
S
11

That's because you're creating a new MaterialPageRoute.

Try this:

onLongPress: () {
  _saved.remove(pair);
  Navigator.of(context).pop();
  _pushSaved();
},


With this solution you'll still see the view change. If you want to prevent that, you'll need a new stateful page, and a bit of refactoring:

  • Make your _saved items global (only for this example)
  • Remove the _pushSaved method
  • Update the onPressed function that used to call the _pushSaved function
  • Add the stateful DetailPage instead of the _pushSaved method

Like so:

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(new MyApp());

// create a global saved set
Set<WordPair> savedGlobal = new Set<WordPair>();

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  RandomWordsState createState() => new RandomWordsState();
}

class RandomWordsState extends State<RandomWords> {
  final List<WordPair> _suggestions = <WordPair>[];
  final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: const Text('Startup Name Generator'),
        actions: <Widget>[
          // change the onPressed function
          new IconButton(icon: const Icon(Icons.list), onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => DetailPage()
              )
            );
          }),
        ],
      ),
      body: _buildSuggestions(),
    );
  }

  Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      itemBuilder: (BuildContext _context, int i) {
        if (i.isOdd) {
          return const Divider();
        }
        final int index = i ~/ 2;
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      });
  }

  Widget _buildRow(WordPair pair) {
    final bool alreadySaved = savedGlobal.contains(pair);

    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
      trailing: new Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
      onTap: () {
        setState(() {
          if (alreadySaved) {
            savedGlobal.remove(pair);
          } else {
            savedGlobal.add(pair);
          }
        });
      },
    );
  }
}

// add a new stateful page
class DetailPage extends StatefulWidget {
  @override
  _DetailPageState createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
  final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);

  @override
  Widget build(BuildContext context) {

    Iterable<ListTile> tiles = savedGlobal.map((WordPair pair) {
      return new ListTile(
        onLongPress: () {
          setState(() {
            savedGlobal.remove(pair);
          });
        },
        title: new Text(
          pair.asPascalCase,
          style: _biggerFont,
        ),
      );
    });

    final List<Widget> divided = ListTile.divideTiles(
      context: context,
      tiles: tiles,
    ).toList();

    return new Scaffold(
      appBar: new AppBar(
        title: const Text('Saved Suggestions'),
      ),
      body: new ListView(children: divided),
    );
  }
}
Steerageway answered 13/3, 2019 at 13:39 Comment(5)
This worked but you will see the mainpage and switch to the new route.Psychologist
You're right. I'll include a solution that prevents that (though the solution won't be as trivial as before)Steerageway
So to summarize. If I'd like to edit the content of a page I should create a new Page and push that page to the navigator. To display content which does not update on the same page I could create the page threw Navigator.of(context).push( new MaterialPageRoute(Psychologist
Yes and no, before you were already creating a second page but had no way of updating the state of it. Now we "prepared" a stateful second page, and told the first page to display that instead of creating one on the fly.Steerageway
This state concept may seem a bit abstract. This page on the Flutter site may clarify things!Steerageway
N
12

2019-November Latest way

more information https://www.youtube.com/watch?v=iEMgjrfuc58

                 ListView.builder/GridView.builder(
                   itemBuilder: (BuildContext context, int index){
                      return Dismissible(
                      key: Key(selectServiceLocations[index].toString()),
                      onDismissed: (direction) {
                        setState(() {
                         selectServiceLocations.removeAt(index);
                        });
                      },
                     child: Container(...)
}
)
Nicolettenicoli answered 12/11, 2019 at 14:24 Comment(2)
Thanks nice suggestion that's really neat.Hinshaw
Great! Here is a friendly official example of Dismissible usage: flutter.dev/docs/cookbook/gestures/dismissibleExemplar
S
11

That's because you're creating a new MaterialPageRoute.

Try this:

onLongPress: () {
  _saved.remove(pair);
  Navigator.of(context).pop();
  _pushSaved();
},


With this solution you'll still see the view change. If you want to prevent that, you'll need a new stateful page, and a bit of refactoring:

  • Make your _saved items global (only for this example)
  • Remove the _pushSaved method
  • Update the onPressed function that used to call the _pushSaved function
  • Add the stateful DetailPage instead of the _pushSaved method

Like so:

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(new MyApp());

// create a global saved set
Set<WordPair> savedGlobal = new Set<WordPair>();

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  RandomWordsState createState() => new RandomWordsState();
}

class RandomWordsState extends State<RandomWords> {
  final List<WordPair> _suggestions = <WordPair>[];
  final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: const Text('Startup Name Generator'),
        actions: <Widget>[
          // change the onPressed function
          new IconButton(icon: const Icon(Icons.list), onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => DetailPage()
              )
            );
          }),
        ],
      ),
      body: _buildSuggestions(),
    );
  }

  Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      itemBuilder: (BuildContext _context, int i) {
        if (i.isOdd) {
          return const Divider();
        }
        final int index = i ~/ 2;
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      });
  }

  Widget _buildRow(WordPair pair) {
    final bool alreadySaved = savedGlobal.contains(pair);

    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
      trailing: new Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
      onTap: () {
        setState(() {
          if (alreadySaved) {
            savedGlobal.remove(pair);
          } else {
            savedGlobal.add(pair);
          }
        });
      },
    );
  }
}

// add a new stateful page
class DetailPage extends StatefulWidget {
  @override
  _DetailPageState createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
  final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);

  @override
  Widget build(BuildContext context) {

    Iterable<ListTile> tiles = savedGlobal.map((WordPair pair) {
      return new ListTile(
        onLongPress: () {
          setState(() {
            savedGlobal.remove(pair);
          });
        },
        title: new Text(
          pair.asPascalCase,
          style: _biggerFont,
        ),
      );
    });

    final List<Widget> divided = ListTile.divideTiles(
      context: context,
      tiles: tiles,
    ).toList();

    return new Scaffold(
      appBar: new AppBar(
        title: const Text('Saved Suggestions'),
      ),
      body: new ListView(children: divided),
    );
  }
}
Steerageway answered 13/3, 2019 at 13:39 Comment(5)
This worked but you will see the mainpage and switch to the new route.Psychologist
You're right. I'll include a solution that prevents that (though the solution won't be as trivial as before)Steerageway
So to summarize. If I'd like to edit the content of a page I should create a new Page and push that page to the navigator. To display content which does not update on the same page I could create the page threw Navigator.of(context).push( new MaterialPageRoute(Psychologist
Yes and no, before you were already creating a second page but had no way of updating the state of it. Now we "prepared" a stateful second page, and told the first page to display that instead of creating one on the fly.Steerageway
This state concept may seem a bit abstract. This page on the Flutter site may clarify things!Steerageway
A
7

A bit late to the party but what I've found is that if you want to manipulate a ListView or GridView it is paramount that you assign a Key to each child Widget of the List/GridView

In short Flutter compares widgets only by Type and not state. Thus when the state is changed of the List represented in the List/GridView, Flutter doesn't know which children should be removed as their Types are still the same and checks out. The only issue Flutter picks up is the number of items, which is why it only removes the last widget in the List/GridView.

Therefore, if you want to manipulate lists in Flutter, assign a Key to the top level widget of each child. A more detailed explanation is available in this article.

This can be achieved be adding

   return GridView.count(
  shrinkWrap: true,
  crossAxisCount: 2,
  crossAxisSpacing: 5.0,
  mainAxisSpacing: 5.0,
  children: List.generate(urls.length, (index) {
    //generating tiles with from list
    return   GestureDetector(
        key: UniqueKey(), //This made all the difference for me
        onTap: () => {
          setState(() {
            currentUrls.removeAt(index);
          }) 

        },
        child: FadeInImage( // A custom widget I made to display an Image from 
            image:  NetworkImage(urls[index]),
            placeholder: AssetImage('assets/error_loading.png') 
            ),

    );
  }),

);
Aegospotami answered 17/10, 2020 at 21:18 Comment(0)
S
1

STEP 1:FIND THE INDEX Of PARTICULAR OBJECT(element) VIA

List_Name.indexOf(List_Name[index]);

ex:_userTransactions.indexOf(_userTransactions[index]) 

Step 2:REMOVE THE OBJECT AT PARTICULAR INDEX

LIST_Name.removeAt(index);

ex: _userTransactions.removeAt(index);
Smilacaceous answered 17/2, 2021 at 12:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.