While viewing a playlist on screen in my app, you have the option to delete the whole playlist.

The current screen being viewed is the playlist detail screen. It has a dependency which is the playlist itself (Drones & AI) that I'm trying to delete. I need both Nav.pop()'s to complete and the widget tree of this screen to be disposed before I delete the playlist.

enter image description here

Once you press delete it calls Nav.pop() twice, once to remove the popup modal, and once to navigate back to the playlists page. I need to have completed the transition back to the playlists screen before deleting the playlist thats currently on screen with removePlaylist(), otherwise the dependency for this screen doesn't exist while it's still in view, hence the error. Currently, during transition to the previous screen you can see an error being thrown as the item gets deleted but is still partially displayed on screen. The item doesn't exist anymore obviously which is why the error is being thrown, but how do I avoid this error? I've tried using Future.delayed then deleting the item, but the screen is already disposed and the state is unstable at the time of execution.

error: Bad state: No element

removePlaylist():

class PlaylistsLocal with ChangeNotifier {
  var singleton = Singleton();

  List<Playlist> _items = [];

  List<Playlist> get items {
    return [..._items];
  }

  void removePlaylist(Playlist item) {
    List<Playlist> newItems = [..._items];
    newItems.removeWhere((Playlist playlist) => item.title == playlist.title);

    savePlaylist(newItems);

    _items = newItems;
    notifyListeners();
  }
}

Bottom Modal class:

class BuildModalBottomPlaylistEdit extends StatelessWidget {
  final Playlist playlist;
  BuildModalBottomPlaylistEdit(this.playlist);
  @override
  Widget build(BuildContext context) {
    return Container(
      height: Singleton.instance.screenSize.height * .21,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(15, 10, 8, 10),
            child: Text(
              playlist.title ,
              style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
            ),
          ),
          ListTile(
            leading: Icon(
              Icons.edit,
              color: Colors.black87,
            ),
            title: Text('Edit playlist'),
          ),
          ListTile(
            leading: Icon(
              Icons.delete,
              color: Colors.black87,
            ),
            title: Text('Delete playlist'),
            onTap: () {
              Navigator.of(context).pop();
              Navigator.of(context).pop();

              Provider.of<PlaylistsLocal>(context, listen: false)
                  .removePlaylist(playlist);
            },
          ),
        ],
      ),
    );
  }
}


Solution 1: Ankush Chavan

With your current code, the navigation will be made to the playlist screen and after that, it will delete the playlist. This is causing the error.
The possible solution would be after doing the first navigation(removing popup modal), delete the playlist, and then navigate back to the playlist screen.
Move Provider.of<PlaylistsLocal>(context, listen: false).removePlaylist(playlist); in between the two Navigator.of(context).pop();.

Refer to this code snippet:

class BuildModalBottomPlaylistEdit extends StatelessWidget {
  final Playlist playlist;
  BuildModalBottomPlaylistEdit(this.playlist);
  @override
  Widget build(BuildContext context) {
    return Container(
      height: Singleton.instance.screenSize.height * .21,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(15, 10, 8, 10),
            child: Text(
              playlist.title ,
              style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
            ),
          ),
          ListTile(
            leading: Icon(
              Icons.edit,
              color: Colors.black87,
            ),
            title: Text('Edit playlist'),
          ),
          ListTile(
            leading: Icon(
              Icons.delete,
              color: Colors.black87,
            ),
            title: Text('Delete playlist'),
            onTap: () {
              Navigator.of(context).pop();
              Provider.of<PlaylistsLocal>(context, listen: false)
                  .removePlaylist(playlist);
              Navigator.of(context).pop();
            },
          ),
        ],
      ),
    );
  }
}


Solution 2: CharlyKeleb

Try this code snippet.

 class BuildModalBottomPlaylistEdit extends StatelessWidget {
      final Playlist playlist;
      BuildModalBottomPlaylistEdit(this.playlist);
      @override
      Widget build(BuildContext context) {
        return Container(
          height: Singleton.instance.screenSize.height * .21,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Padding(
                padding: const EdgeInsets.fromLTRB(15, 10, 8, 10),
                child: Text(
                  playlist.title ,
                  style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
                ),
              ),
              ListTile(
                leading: Icon(
                  Icons.edit,
                  color: Colors.black87,
                ),
                title: Text('Edit playlist'),
              ),
              ListTile(
                leading: Icon(
                  Icons.delete,
                  color: Colors.black87,
                ),
                title: Text('Delete playlist'),
                onTap: ()aysnc{
                  Navigator.of(context).pop();

                 await Provider.of<PlaylistsLocal>(context, listen: false)
                      .removePlaylist(playlist);
                   Navigator.of(context).pop();
                },
              ),
            ],
          ),
        );
      }
    }


Solution 3: Baker

Jacob,

I mocked up a Playlists page, a Playlists Detail page, and a ModalDialog that pops up above Detail page where you can delete the playlist.

I've copied your PlaylistsLocal clas, with placeholders for missing interface items (Singleton, savePlaylist).

But, I couldn't get the error you're seeing.

I don't believe the error is related to the popping of the Modal, nor of the DetailPage, as both of those are popped / gone before the removePlaylist method is called. The DetailPage is stateless (in my example below) and doesn't rebuild based on new data.

Whenever you see Bad State: No element, this means you're attempting to call methods on a List which is empty and that error will be thrown. It's a pretty vague error unfortunately.

Here's my mock code, which hopefully you can compare to your own to find out where your error might be coming from.

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<PlaylistsLocal>(
        create: (context) => PlaylistsLocal(),
      child: MaterialApp(
        home: HomePage(),
      )
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text('Playlists', style: TextStyle(fontSize: 30),),
            Expanded(
                flex: 8,
                child: ListView.builder(
                    itemCount: Provider.of<PlaylistsLocal>(context).items.length,
                    itemBuilder: (context, i) {
                      Playlist playlist = Provider.of<PlaylistsLocal>(context).items[i];
                      return Container(
                        color: Colors.yellow,
                        child: ListTile(
                          title: InkWell(
                            child: Text(playlist.title),
                            onTap: () => Navigator.of(context).push(
                                MaterialPageRoute(builder: (context) => PlaylistDetailPage(playlist))
                            ),
                          ),
                        ),
                      );
                    }
                )
            ),
          ],
        ),
      ),
    );
  }
}

/// Playlist detail page uses Playlist object for title data
class PlaylistDetailPage extends StatelessWidget {
  final Playlist playlist;

  PlaylistDetailPage(this.playlist);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: InkWell(
        child: Container(
          color: Colors.yellow,
          child: Center(
            child: Text('${playlist.title} DETAILS Page\n (click anywhere)'),
          ),
        ),
        onTap: () => showDialog(
          context: context,
          barrierDismissible: true,
          builder: (context) => BottomModalPlaylistEdit(playlist),
        ),
      ),
    );
  }
}

class BottomModalPlaylistEdit extends StatelessWidget {
  final Playlist playlist;

  BottomModalPlaylistEdit(this.playlist);

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
        title: Text(playlist.title),
        content: Text('Playlist details are here'),
        actions: [
          RaisedButton(child: Text('Back'), onPressed: Navigator.of(context).pop),
          RaisedButton(child: Text('Delete'),
            onPressed: () {
              Navigator.of(context).pop();
              Navigator.of(context).pop();
              Provider.of<PlaylistsLocal>(context, listen: false)
                  .removePlaylist(playlist);
            },
          )
        ]
    );
  }
}

class Playlist {
  String title;

  Playlist(this.title);

}

class Singleton {}

class PlaylistsLocal with ChangeNotifier {
  var singleton = Singleton();

  List<Playlist> _items = [Playlist('Hairband Hits')];

  List<Playlist> get items {
    return [..._items];
  }

  void removePlaylist(Playlist item) {
    List<Playlist> newItems = [..._items];
    newItems.removeWhere((Playlist playlist) => item.title == playlist.title);

    savePlaylist(newItems);

    _items = newItems;
    notifyListeners();
  }

  void savePlaylist(List<Playlist> items) {
    // Not sure what you do here
  }

  void resetPlaylist() {
    _items = [Playlist('Hairband Hits')];
  }
}

To make repeated testing easier, the ListView.builder can be wrapped in a Consumer to rebuild the list of Playlists when button resets the Items in the PlaylistsLocal provider.

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text('Playlists', style: TextStyle(fontSize: 30),),
            Expanded(
                flex: 8,
                child: Consumer<PlaylistsLocal>(
                  builder: (context, value, child) => ListView.builder(
                      itemCount: Provider.of<PlaylistsLocal>(context).items.length,
                      itemBuilder: (context, i) {
                        Playlist playlist = Provider.of<PlaylistsLocal>(context).items[i];
                        return Container(
                          color: Colors.yellow,
                          child: ListTile(
                            title: InkWell(
                              child: Text(playlist.title),
                              onTap: () => Navigator.of(context).push(
                                  MaterialPageRoute(builder: (context) => PlaylistDetailPage(playlist))
                              ),
                            ),
                          ),
                        );
                      }
                  )
                )
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                RaisedButton(
                  child: Text('Reset'),
                  onPressed: Provider.of<PlaylistsLocal>(context).resetPlaylist,
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

Let me know how you make out with the debugging.


Solution 4: Akshay kumar Jain

You can achieve this with the following logic and remove bad element error -

The UI part is correct. Nothing to change. ie. -

Navigator.of(context).pop();
await Provider.of<PlaylistsLocal>(context, listen: false)
                      .removePlaylist(playlist);
Navigator.of(context).pop();

Now inside the PlaylistsLocal Provider. You have to do little changes in the remote playlist Function. Add Future.delayed methods Like this -

    Future<void> PlaylistsLocal(String? notesId)async  {
      Future.delayed(const Duration(seconds: 1), ()async {

       // Your Code ---

        notifyListeners();
});
}

Now you'll not receive any error. Please thumbs up if you like it.