1

I have a MultiProvider in the main with the following code:

@override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (context) => ReadPreferences(),
        ),
        ChangeNotifierProvider(
          create: (context) => ItemsCrud(),
        ),
      ],
      child: MaterialApp(...

I am using shared preferences to save and updated the last opened list, so the following in my ReadPreferences file:

import 'package:flutter/foundation.dart'; //To use the "ChangeNotifier"
import 'package:shared_preferences/shared_preferences.dart'; //local store

class ReadPreferences extends ChangeNotifier {
  Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
  String openedList = '';

  //Constructor method
  ReadPreferences() {
    getPreferences();
  }

  void getPreferences() async {
    final SharedPreferences prefs = await _prefs;
    openedList = prefs.getString('openedList');
  }

  Future<bool> updateOpenedList({String listTitle}) async {
    final SharedPreferences prefs = await _prefs;
    bool result = await prefs.setString('openedList', listTitle);
    if (result == true) {
      openedList = listTitle;
    }
    
    notifyListeners();

    return result;
  }
}

When I'm trying to update the opened list it updates in the shared Preferences file normally but it never listen to the new "openedList" value in my homepage screen.

The code I use in the homepage screen like the following:

child: Text(Provider.of<ReadPreferences>(context).openedList),
    

I checked many times by printing the new value inside the "ReadPreferences" files, but outside it, it keeps give me the old value not the updated one at all.

James Z
  • 12,209
  • 10
  • 24
  • 44
Fidz
  • 33
  • 6

2 Answers2

0

I tested with a modified Flutter Counter (default app), everything seams to be working fine. Note that I'm not calling setState() anywhere, so the only refresh is coming from the ReadPreferences class.

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

class ReadPreferences extends ChangeNotifier {
  Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
  String openedList = '';

  //Constructor method
  ReadPreferences() {
    getPreferences();
  }

  void getPreferences() async {
    final SharedPreferences prefs = await _prefs;
    openedList = prefs.getString('openedList');
  }

  Future<bool> updateOpenedList({String listTitle}) async {
    final SharedPreferences prefs = await _prefs;
    bool result = await prefs.setString('openedList', listTitle);
    if (result == true) {
      openedList = listTitle;
    }

    notifyListeners();

    return true;
  }
}

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
        providers: [
          ChangeNotifierProvider(
            create: (context) => ReadPreferences(),
          )
        ],
        child: MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        ));
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(Provider.of<ReadPreferences>(context).openedList)
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _counter++;
          Provider.of<ReadPreferences>(context, listen: false).updateOpenedList(listTitle: (_counter).toString());
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
Andrija
  • 1,534
  • 3
  • 10
  • So why it keeps displaying the old value for me? Is there anything I should do? – Fidz Mar 14 '21 at 19:25
  • Give us a bit more of your code if possible. – Andrija Mar 15 '21 at 05:51
  • That's the HomeScreen part: `class HomeScreen extends StatelessWidget { static const String screenId = 'home_screen'; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('List App'), actions: [ //if you want to add something beside the title Center( child: Text(Provider.of(context).openedList), ), ....` – Fidz Mar 15 '21 at 14:11
  • And on the page you choose the list from: `trailing: IconButton( icon: Icon(Icons.check), onPressed: () async { ReadPreferences readPref = ReadPreferences(); bool result = await readPref.updateOpenedList( listTitle: list.title); if (result == true) { Navigator.pop(context); } }), );` – Fidz Mar 15 '21 at 14:39
  • I think the issue is that you are updating your state before the old screen is visible.Try this: https://stackoverflow.com/questions/49804891/force-flutter-navigator-to-reload-state-when-popping – Andrija Mar 15 '21 at 14:46
  • Ok, I finally found what's wrong, I was using this to call the update method `ReadPreferences readPref = ReadPreferences(); bool result = await readPref.updateOpenedList( listTitle: list.title);` It's suppose to do the update using this `Provider.of(context, listen: false) .updateOpenedList(listTitle: list.title);` It is now updating to the new value, could you just explain to me why I shouldn't use the 1st method? Do I have to use `Provider.of` in update to trigger the `notifyListeners()` – Fidz Mar 15 '21 at 21:12
  • 1
    You are right, you should be using Provider.of. When you add Provider using ````ChangeNotifierProvider(create: (context) => ReadPreferences(), )```` - new instance of ReadPreferences() is created, and it is kept in WidgetTree. This is the instance you want, and you get it by using Provider.of. In your code above, you created a new instance of ReadPreferences - and this is where you added a new value. This new instance has nothing to do with the one that Provider manages, and this new instance has nothing to do with your Widget. – Andrija Mar 16 '21 at 10:09
0

I finally found the answer, many thanks for @Andrija explanation. What I was doing wrong is to create a new instance from ReadPreferences() then using it for the update method, but the correct approach is to use Provider.of<ReadPreferences>(context, listen: false).updateOpenedList(listTitle: list.title); to use the update method.

For more explanation I'll add @Andrija comment hereafter:-

You are right, you should be using Provider.of. When you add Provider using ChangeNotifierProvider(create: (context) => ReadPreferences(), ) - new instance of ReadPreferences() is created, and it is kept in WidgetTree. This is the instance you want, and you get it by using Provider.of. In your code above, you created a new instance of ReadPreferences - and this is where you added a new value. This new instance has nothing to do with the one that Provider manages, and this new instance has nothing to do with your Widget.

Fidz
  • 33
  • 6