13

I am using Provider for managing the state of my app. Here's how I am implementing it.

hypnose.dart

class _HypnoseAppState extends State<HypnoseApp> {
  @override
  Widget build(BuildContext context) {
    UserService userService = UserService();
    AudioUtilService audioUtilService = AudioUtilService();

    return MultiProvider(
      providers: [
        ChangeNotifierProvider<UserService>.value(
          value: userService,
        ),
        ChangeNotifierProvider<AudioUtilService>.value(
          value: audioUtilService,
        )
      ],
      child: MaterialApp(
          debugShowCheckedModeBanner: false,
          title: Globals.title,
          theme: ThemeData(primarySwatch: Colors.cyan),
          darkTheme: ThemeData.dark(),
          initialRoute: '/',
          routes: {
            '/': (BuildContext context) => WelcomeScreen(userService),
            '/home': (BuildContext context) => HomePageSwitcher(),
            '/audiocreate': (BuildContext context) => AudioCreateScreen()
          }),
    );
  }
}

home_switcher.dart

class HomePageSwitcher extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<UserService>(
      builder: (BuildContext context, UserService userService, Widget child) {
        return Scaffold(
            appBar: AppBar(),
            drawer: Drawer(
              child: Column(
                children: <Widget>[
                  UserAccountsDrawerHeader(
                    accountEmail: Text(userService.loggedInUser.email),
                    accountName: Text(userService.loggedInUser.name),
                    currentAccountPicture:
                        Image.network(userService.loggedInUser.avatar),
                  )
                ],
              ),
            ),
            body: Center(
              child: RaisedButton(
                child: Text('Sign out'),
                onPressed: () async {
                  await userService.signOut();
                  Navigator.pushNamed(context, '/');
                },
              ),
            ));
      },
    );
  }
}

user_service.dart

class UserService extends ChangeNotifier {
  // Get auth instances
  final GoogleSignIn _googleSignIn = GoogleSignIn();
  final FirebaseAuth _auth = FirebaseAuth.instance;

  // Store reference of user collection
  final CollectionReference userDb = Firestore.instance.collection('user');

  // Master instance of logged in user
  User _loggedInUser;

  // Getter to access loggedInUser
  User get loggedInUser {
    return _loggedInUser;
  }

  PublishSubject<AuthState> _authStateSubject = PublishSubject();

.... other code

Now the problem here is that every time I hot reload, on the home page, I start to get the NoSuchMethodError as it says that properties like email, name etc. were called on null, which I think means that the state is lost. How can I overcome the same? Am I doing something wrong?

Ayush Shekhar
  • 1,483
  • 4
  • 16
  • 32

3 Answers3

26

You should not use ChangeNotifierProvider.value. Instead use the default constructor:

ChangeNotifierProvider(
  builder: (_) => UserService(),
)

Otherwise, your build method is impure and you'll have issues like described in How to deal with unwanted widget build?

Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • I have seen many examples that says UserService() instead of the object userService. does it have to be a newly instantiated object? Because I cannot figure out how to use that with newly set values. – chitgoks Jan 28 '20 at 11:05
  • @chitgoks as stated by the doc, if you already have an instance of object you can use `ChangeNotifierProvider.value` – Rémi Rousselet Jan 28 '20 at 12:19
  • i used ProxyProvider instead. since my variable properties change. – chitgoks Jan 29 '20 at 02:02
  • 6
    I have exactly the same issue, even with `create: (_) => UserService(),` on `4.0.4` – ad_on_is Feb 13 '20 at 19:06
  • @RémiRousselet you are awesome. by following your link. I came to know provider will also lose state if you do the routes. before notifyListener(). – Shahryar Rafique Aug 18 '21 at 10:54
  • Nice, how have I only just come across this! – Chris Jan 31 '23 at 10:09
8

The build method is designed in such a way that it should be pure/without side effects. This is because many external factors can trigger a new widget build, such as:

Route pop/push
Screen resize, usually due to keyboard appearance or orientation change
Parent widget recreated its child
An InheritedWidget the widget depends on (Class.of(context) pattern) change

This means that the build method should not trigger an http call or modify any state.

How is this related to the question?

The problem you are facing is that your build method has side-effects/is not pure, making extraneous build call troublesome.

Instead of preventing build call, you should make your build method pure, so that it can be called anytime without impact.

In the case of your example, you'd transform your widget into a StatefulWidget then extract that HTTP call to the initState of your State:

ChangeNotifierProvider(
    create: (_) => UserService(),
),
NIZ ART
  • 81
  • 1
  • 1
0

Keep the key

class _HypnoseAppState extends State<HypnoseApp> {
    Key key = UniqueKey();
    ...
}

and build:

return MultiProvider(
    key: key, //<<<<<<<<<<<<<<<<<<<<<<Here
    providers: ChangeNotifierProvider<UserService>.value(
        value: userService,
    ),
    ChangeNotifierProvider<AudioUtilService>.value(
         value: audioUtilService,
     )
],
Tyler2P
  • 2,324
  • 26
  • 22
  • 31