32

I am refactoring my Flutter application code by adding Provider as a state management.

Desired behavior: When Home screen opens, app should check if users email verified, if it's not then should show dialog popup.

Problem: It worked fine when I was passing data for EmailVerified through the constructor, but if I want to use Provider, I can't get this data at initState() lifecycle.

Can you please recommend me correct approach for such use case?

import 'package:myapp/services/authentication.dart';
import 'package:myapp/screens/settings_screen.dart';
import 'package:flutter/material.dart';
import 'package:myapp/services/authentication.dart';
import 'package:provider/provider.dart';

class HomeScreen extends StatefulWidget {

  @override
  State<StatefulWidget> createState() => new _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final GlobalKey<FormState> formKey = GlobalKey<FormState>();
  bool _isEmailVerified = false;

  @override
  void initState() {
    super.initState();
    _checkEmailVerification(); // <=== Method which should show Dialog box if email is not verified which is coming from "Auth" Provider
  }

  @override
  Widget build(BuildContext context) {
    final auth = Provider.of<Auth>(context, listen: false); // <==== Service from Provider, which contains data for _isEmailVerified
    auth.isEmailVerified().then((value) => _isEmailVerified = value);

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('My App'),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Text(
              'Welcome to my app',
            ),
          ],
        ),
      ),
    );
  }

  void _checkEmailVerification() async {
    _isEmailVerified = auth.isEmailVerified(); // <=== How can I use "auth" from Provider to get isEmailVerified data ????
    if (!_isEmailVerified) {
      _showVerifyEmailDialog();
    }
  }

  void _showVerifyEmailDialog() {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        // return object of type Dialog
        return AlertDialog(
          title: new Text("Verify your account"),
          content: new Text("Please verify account in the link sent to email"),
          actions: <Widget>[
            new FlatButton(
              child: new Text("Resend link"),
              onPressed: () {
                Navigator.of(context).pop();
                _resentVerifyEmail();
              },
            ),
            new FlatButton(
              child: new Text("Dismiss"),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }

  void _resentVerifyEmail() {
    // Logic to send email
  }
}

Maxim Vershinin
  • 531
  • 1
  • 6
  • 12

6 Answers6

84

You need to use the context to call Provider.of() so you can add addPostFrameCallback() which is called after the first build, there you can use the context

@override
void initState() {
    super.initState();

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      auth = Provider.of<Auth>(context, listen: false);
    });
}
Delgan
  • 18,571
  • 11
  • 90
  • 141
Sebastian
  • 3,666
  • 2
  • 19
  • 32
  • 2
    Thanks a lot @Sebastian . Also if someone else facing this issue, I recommend following article, it explains why `showDialog` doesn't work as you might expect and what is `addPostFrameCallback ()` needed for - https://www.didierboelens.com/faq/week2/ – Maxim Vershinin Feb 21 '20 at 18:37
  • @ Sebastian This worked in Android, and not in iOS. In iOS throws error Unhandled Exception: 'package:provider/src/provider.dart': Failed assertion: line 240 pos 12: 'context != null': is not true. Thanks. – Kerim Amanov Dec 05 '20 at 04:44
  • 2
    Does this really work or am I missing something? My user is not initialized during build time and dart throws an error. I am using `Provider.of(context, listen:false).currentUser` in the `addPostFrameCallback()` – Chirag Arora Jan 17 '21 at 18:19
  • 1
    Thanks man. Struggling this on several hours. – Hiran Walawage Jul 06 '21 at 15:03
  • Tried it but still gets the error message: `The following ProviderNotFoundException was thrown during a scheduler callback: Error: Could not find the correct Provider...` – Giraldi Sep 13 '22 at 16:47
  • ... is it because my `NotifierProvider` is in the same widget instead of in the parent? – Giraldi Sep 13 '22 at 18:35
13

From Provider v4.1.0, you can also use read() method. It reduces the boilerplate code.

@override
void initState() {
  super.initState();

  WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
    auth = context.read<Auth>();
  });
}
Vinoth Vino
  • 9,166
  • 3
  • 66
  • 70
4

You can make your initState as follows:

@override
void initState() {
    super.initState();
    final myModel = Provider.of<YourProivder>(context, listen: false);
}
Nikhith sunil
  • 229
  • 3
  • 4
1

You can also pass context to HomeScreen widget, which will let you access Provider :

class HomeScreen extends StatefulWidget {
  final BuildContext context;
  HomeScreen(this.context);

  @override
  State<StatefulWidget> createState() => new _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  bool _isEmailVerified = false;

  void _checkEmailVerification() async {
    _isEmailVerified = widget.context.read<Auth>().isEmailVerified();
    if (!_isEmailVerified) {
      _showVerifyEmailDialog();
    }
  }

...

}
oblomov
  • 426
  • 6
  • 9
0

For this, we need to bindwidget. so provider give us this feature too

 WidgetsBinding.instance.addPostFrameCallback((_) {
      auth = Provider.of<Auth>(context, listen: false);
     _isEmailVerified = auth.isEmailVerified()

    });
Karamat Subhani
  • 104
  • 1
  • 7
-5

In version 3.0.0+1,you can also do

you can do it with

var CallNotifier auth;


@override
void initState(){
 super.initState();
 auth = Provider.of<CallNotifier>(context, listen = false);
}