How does listeners in Provider work in Flutter?

Issue

I am using Provider package to work with data from different locations. For this question, I have created a sample project which consists of a Welcome Screen which shows a Login button, upon click, it redirects to Login Screen where one would have login text field but for now, I have placed a button which on click calls the login function and that login function notifies the listeners. I also have a logout function and boolean value which keeps track of the logged in status.

These functions and values reside in a file called auth.provider.dart in providers directory in lib folder.

import 'package:flutter/foundation.dart';

class Auth with ChangeNotifier {
  var _loggedIn = false;

  bool get loggedIn => _loggedIn;

  void loginUser() {
    _loggedIn = true;
    print('Logged in');
    notifyListeners();
  }

  void logout() {
    _loggedIn = false;
    print('Logged out');
    notifyListeners();
  }
}

I am providing the provider in main.dart and also using Consumer:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Auth>(
      create: (_) => Auth(),
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: Consumer<Auth>(
          builder: (_, auth, __) {
            print('main.dart logged in: ${auth.loggedIn}');
            if(auth.loggedIn) return HomeScreen();
            return WelcomeScreen();
          },
        ),
        routes: {
          LoginScreen.routeName: (ctx) => LoginScreen(),
          HomeScreen.routeName: (ctx) => HomeScreen(),
        },
      ),
    );
  }
}

In the above file, I simply check whether loggedIn is true or not. If it is not then show the welcome screen so the user can login but if the user is logged in then directly show the home screen. Have also put a print statement in Consumer’s builder so it prints the status whenever it gets notified of any changes in auth data. This runs once when the app starts and never again.

There is not much in the login screen, just a scaffold and a login button which calls the login function.

RaisedButton(
    child: Text('Click to Login'),
    onPressed: () {
      Provider.of<Auth>(context, listen: false).loginUser();
    },
),

Now on login button click, it sets the loggedIn boolean to true and notifies the listener which should be Consumer but Consumer in main.dart is never notified of it. It does not trigger the change, there is a condition which checks if logged in is true then show home screen but screen does not change at all. One has to manually use Navigator to move to home screen and on home screen there is a logout button which simply sets loggedIn to false, once that happens it should notify the Consumer but it once again doesn’t.

Which now brings me to the question: How does listeners in Provider work? When does it notify the listeners for changes? Will it not notify if the changes occur more than a level down like in the above example, Consumer is in main.dart and the changes occur 2 levels down main.dart > WelcomeScreen > LoginScreen. How to let the Consumer in main.dart know that the underlying data it is using has changed?

If you want to quickly get started with the above example, here is the repository link.

Solution

I went through your github code.

With pushReplacementNamed to go to login screen

Initially->main.dart->welcomeScreen->(on-clicking-loginbtn)->LoginScreen

Here,

1)Consumer is not listening(because your initial main.dart screen is replaced by login page and doesnt exist on a page currently to listen)

2)There is no navigator to go back(because page is replaced not in a stack)

With pushNamed to go to login screen

Initially->main.dart->welcomeScreen->(on-clicking-loginbtn)->LoginScreen

Here,

1)Consumer is listening(because your initial main.dart screen is not replaced and exist on a page currently.But you are not seeing the changes because you have covered your main intial page with login screen above in the stack )

2)There is navigator to go back to HomeScreen(Now you can go back to see changes using back arrow)

Conclusion Page will not able to listen to changed Provider Value if the page is replaced by other page. It should be there in a screen listening.

Pattern that can be used

main.dart(home argument)

home: Consumer<Auth>(
          builder: (_, auth, __) {
            print('main.dart logged in: ${auth.loggedIn}');
            if(auth.loggedIn) return HomeScreen();
            return WelcomeScreen();
          },
        )

WelcomeScreen(login code)

 RaisedButton(
              child: Text('Login'),
              onPressed: () => Navigator.of(context).pushReplacementNamed(LoginScreen.routeName),
            )

LoginScreen(login button)

RaisedButton(
          child: Text('Click to Login'),
          onPressed: () {
            Provider.of<Auth>(context, listen: false).loginUser();
            Navigator.of(context).pushReplacementNamed('/');
          },
        )

HomeScreen(signout btn)

RaisedButton(
          child: Text('Logout'),
         onPressed: () {
            Provider.of<Auth>(context, listen: false).logout();
            Navigator.of(context).pushReplacementNamed('/');
          }

Answered By – Dipesh KC

Answer Checked By – Cary Denson (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *