Pheepster
Pheepster

Reputation: 6367

Flutter provider doesn't respond to notifyListeners

I have an app that allows users to view river gauge data from an API that lists rivers from the entire US. Users can save gauges as favorites which are then displayed in a favorites view. The favorites are stored in user defaults by ID. The favorites view is populated through a favorites view model which is constructed using the user defaults. The favorites are displayed in a list view and when one is tapped the app loads a details view. In the details view, the user can opt to delete the favorite. When returning to the favorites view using the back button, the favorites view should reflect the change, but it does not. I am using Flutter Provider and this is my first implementation of it.

I am defining the provider in main since I will need access to it throughout the app.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  setupServiceLocator();
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown])
      .then((_) {
    runApp(
      ChangeNotifierProvider(
        create: (context) => FavoritesViewModel(),
        child: MyApp(),
      )
    );
  });
} 

My ListView

class _FavoritesView extends State<FavoritesView> {
  FavoritesViewModel viewModel = FavoritesViewModel(); // serviceLocator<FavoritesViewModel>();

  @override
  void initState() {
    viewModel.loadFavorites();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<FavoritesViewModel>(
      builder: (context, model, child) => Scaffold(
        appBar: AppBar(
          title: Text('=Favorites='),
        ),
        body: ListView.builder(
          itemCount: model.favorites.length,
          itemBuilder: (context, index) {
            return FavoriteCard(model.favorites[index], Key(model.favorites[index]));
          },
        ),
      ),
    );
  }

Favorites View Model

class FavoritesViewModel extends ChangeNotifier {

  List<String> favorites;

  FavoritesViewModel() {
    loadFavorites();
  }

  void deleteFavorite(String id) {
    if (favorites.contains(id)) {
      this.favorites.remove(id);
    }
    Storage.initializeList(kFavoritesKey, favorites);
    notifyListeners();
  }

In the Details view (one place where users can opt to remove a favorite) I am getting a reference to the favorites view model

FavoritesViewModel favesVM = FavoritesViewModel(); // serviceLocator<FavoritesViewModel>();

and calling its deleteFavorite function from within a dialog:

var approveRemovalButton = FlatButton(
  child: Text('Remove',
    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.blue),
  ),
  onPressed: () {
      animationDuration = 0;
      favesVM.deleteFavorite(widget.gaugeId);
      Navigator.pop(context);
  },
);

When I tap a button and invoke the deleteFavorite function on the view model, the item is removed from user preferences, and is also removed from the view model, however, upon returning to the favorites list view, the item is still present in the UI. It's as if the Favorites list view is not registered as a listener and doesn't respond to notifyListeners. Can anyone see what might be causing this? Thanks!

Upvotes: 0

Views: 1024

Answers (3)

dangngocduc
dangngocduc

Reputation: 1824

Update your State<FavoritesView> like bellow:

class _FavoritesView extends State<FavoritesView> {
  FavoritesViewModel viewModel; 

  @override
  void initState() {
    super.initState();
    viewModel = Provider.of<FavoritesViewModel>(context, listen: false);
    viewModel.loadFavorites();
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<FavoritesViewModel>(
      builder: (context, model, child) => Scaffold(
        appBar: AppBar(
          title: Text('=Favorites='),
        ),
        body: ListView.builder(
          itemCount: model.favorites.length,
          itemBuilder: (context, index) {
            return FavoriteCard(model.favorites[index], Key(model.favorites[index]));
          },
        ),
      ),
    );
  }

Upvotes: 1

ikerfah
ikerfah

Reputation: 2862

Your issue that you have two view models running at the same time, let me explain how :

By calling FavoritesViewModel viewModel = FavoritesViewModel(); // serviceLocator<FavoritesViewModel>(); This will create an instance from the view model.

And also , by doing this

ChangeNotifierProvider(
        create: (context) => FavoritesViewModel(),
        child: MyApp(),
      )

You are creating another view model

so the tricky part is : you have two view models that both contains the same data, You are using the view model from the consumer (not the locator) to show the data into your listview and then you are deleting the data from the locator , so the delete will not be reflected.

As a solution you can wrap your FavoriteCard by Consumer<FavoritesViewModel> and use model.deleteFavorite()

Upvotes: 2

Sanjay Sharma
Sanjay Sharma

Reputation: 4035

I think you are explicitly creating multiple provides and you loaded data in one provider and when you are notifying the changes, the parent notifier is getting notified and changes are not getting reflected. Don't create separate FavoritesViewModel as you should use the same object. You have already created one object in runApp()

Please replace

FavoritesViewModel viewModel = FavoritesViewModel();

with

FavoritesViewModel viewModel =  Provider.of<FavoritesViewModel>(context);

Upvotes: 1

Related Questions