Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

notifyListeners() of nested Provider is not updating the UI

Tags:

flutter

I'm using provider Flutter package.

I have a main list and each item of the main list has a sublist. I can add items to the main list and it's working but every time I add something to the sublist the UI is not getting updated. Run the code and add some items and open them then add some items there (this is the sublist) and you will see you won't see any update but if you minimize the keyboard the UI will get updated. Maybe because I am using Provider.of<Collection>(context)to access the object of the sublist and notifyListeners() of the CityName class can't run the findById(id) this is what I can think of.

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider.value(
          value: Collection(),
        ),
        ChangeNotifierProvider.value(
          value: CityName(),
        )
      ],
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _textEditingController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              child: ListView(
                  children: Provider.of<Collection>(context)
                      .cityNameCollection
                      .map((collection) {
                return CollectionCard(collection.title, collection.id);
              }).toList()),
            ),
            Container(
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Expanded(
                    child: TextField(
                      controller: _textEditingController,
                      autofocus: true,
                    ),
                  ),
                  FlatButton(
                    onPressed: () {
                      Provider.of<Collection>(context)
                          .addCollection(_textEditingController.text);
                      _textEditingController.clear();
                    },
                    child: Text(
                      'ADD',
                    ),
                  ),
                ],
              ),
            ),
          ]),
    );
  }
}

class CollectionCard extends StatelessWidget {
  final String title;
  final String id;

  CollectionCard(this.title, this.id);
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Card(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20),
          child: Text(title),
        ),
      ),
      onTap: () {
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) => CityNamesList(id),
          ),
        );
      },
    );
  }
}

class CityNamesList extends StatelessWidget {
  final String id;
  CityNamesList(this.id);
  final _textEditingController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    final item = Provider.of<Collection>(context).findById(id);

    return Scaffold(
      appBar: AppBar(
        title: Text(item.title),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Expanded(
            child: ListView(
                children: item.names.map((name) {
              return ListTile(
                title: Text(name),
                trailing: IconButton(
                  icon: Icon(Icons.delete),
                  onPressed: () {
                    item.removeName(name);
                  },
                ),
              );
            }).toList()),
          ),
          Container(
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Expanded(
                  child: TextField(
                    controller: _textEditingController,
                    autofocus: true,
                  ),
                ),
                FlatButton(
                  onPressed: () {
                    item.addName(_textEditingController.text);
                    _textEditingController.clear();
                  },
                  child: Text(
                    'ADD',
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class Collection with ChangeNotifier {
  List<CityName> cityNameCollection = [];

  void addCollection(value) {
    cityNameCollection
        .add(CityName(title: value, id: DateTime.now().toString()));
    notifyListeners();
  }

  CityName findById(id) {
    return cityNameCollection.firstWhere((a) => a.id == id);
  }
}

class CityName with ChangeNotifier {
  String title;
  String id;
  List<String> names = [];

  CityName({this.title, this.id});

  void addName(value) {
    names.add(value);
    notifyListeners();
  }

  void removeName(value) {
    names.remove(value);
    notifyListeners();
  }
}

like image 526
Watery Desert Avatar asked Sep 25 '19 20:09

Watery Desert


1 Answers

There are several problems here

1) You are creating Collection() inside ChangeNotifierProvider.value which can lead to recreating it (not just now but after some changes in code). Instead use ChangeNotifierProvider with builder which will create the value exactly once

2) You are using single ChangeNotifierProvider for CityName while there can be multiple CityName instances. So instead of referring to specific instance of CityName all providers will always give you the same one

3) Passing collection.id and then finding it by id is not necessary and complicates the code. Just pass the collection instance itself

4) CityNamesList dosen't get provided specific CityName but gets it from Collection provider. In this case the UI will not be rebuilt because it does not listen to CityNamesList changes. Actually it listens to Collection changes but Collection doesn't know when its item changes because no one calls notifyListeners() on Collection inside CityName@addName

5) Finally, CityName's constructor fields are optional (not required) which allows to create dummy instances and leads to errors

Here is an example how you can fix this problems (without removeName for simplicity)

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(
      builder: (context) => Collection(),
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _textEditingController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Expanded(
            child: ListView(
              children: Provider.of<Collection>(context)
                  .cityNameCollection
                  .map((item) => CollectionCard(item))
                  .toList(),
            ),
          ),
          Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Expanded(
                child: TextField(
                  controller: _textEditingController,
                  autofocus: true,
                ),
              ),
              FlatButton(
                onPressed: () {
                  Provider.of<Collection>(context)
                      .addCollection(_textEditingController.text);
                  _textEditingController.clear();
                },
                child: Text('ADD'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class CollectionCard extends StatelessWidget {
  final CityName item;

  CollectionCard(this.item);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Card(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20),
          child: Text(item.title),
        ),
      ),
      onTap: () {
        Navigator.of(context).push(
          MaterialPageRoute(
            builder: (context) {
              return ChangeNotifierProvider.value(
                value: item,
                child: CityNamesList(),
              );
            },
          ),
        );
      },
    );
  }
}

class CityNamesList extends StatelessWidget {
  final _textEditingController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    final item = Provider.of<CityName>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text(item.title),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Expanded(
            child: ListView(
              children: item.names.map((name) {
                return ListTile(
                  title: Text(name),
                );
              }).toList(),
            ),
          ),
          Container(
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Expanded(
                  child: TextField(
                    controller: _textEditingController,
                    autofocus: true,
                  ),
                ),
                FlatButton(
                  onPressed: () {
                    item.addName(_textEditingController.text);
                    _textEditingController.clear();
                  },
                  child: Text('ADD'),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class Collection with ChangeNotifier {
  List<CityName> cityNameCollection = [];

  void addCollection(String value) {
    cityNameCollection.add(CityName(title: value));
    notifyListeners();
  }
}

class CityName with ChangeNotifier {
  String title;
  List<String> names = [];

  CityName({@required this.title});

  void addName(value) {
    names.add(value);
    notifyListeners();
  }
}
like image 72
Pavel Avatar answered Nov 15 '22 06:11

Pavel