17

I have problems with FutureBuilder starting twice. First it fetch the data correctly, returning my StartScreen, then after few seconds, the StartScreen rebuilds and I noticed that the FutureBuilder fires again.

Here is my code and it's pretty simple, so I wonder what may the problem be?!?

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  FirebaseUser user;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getNewestlocation();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'APP',
      theme: buildTheme(),
      home: FutureBuilder<FirebaseUser>(
        future: Provider.of<AuthService>(context).getUser(),
        builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            if (snapshot.error != null) {
              print('error');
              return Text(snapshot.error.toString());
            }
            user = snapshot.data;
            print('user here $user');
            return snapshot.hasData ? StartScreen(user) : LoginScreen();
          } else {
            return LoadingCircle();
          }
        },
      ),
    );
  }
}

Can anyone help me with this, please?

Tom Aalbers
  • 4,574
  • 5
  • 29
  • 51
i6x86
  • 1,557
  • 6
  • 23
  • 42

3 Answers3

32

The future is firing again because you're creating it in the build method at the same time as the FutureBuilder.

From the FutureBuilder docs:

The future must have been obtained earlier, e.g. during State.initState, State.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.

So to prevent it from firing you'd have to do something like this:

    class _MyAppState extends State<MyApp> {
      Future<String> _myString;

      @override
      void initState() {
        super.initState();
        _myString = _fetchString();
      }

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: FutureBuilder(
            future: _myString,
            builder: (context, snapshot) {
              // build page stuff...
            },
          ),
        );
      }
    }

    Future<String> _fetchString() async {
      print('running future function');
      await Future.delayed(Duration(seconds: 3));
      return 'potatoes';
    }

Note, to access a provider in initState() you have to set listen to false, as detailed in this answer.

Alfred Jingle
  • 1,236
  • 9
  • 15
  • 1
    That works! But if you use Provider and you have async method which you want to call from Provider, you should use it in didChangeDependencies, because in didChangeDependencies you can get a context, not in initState. That is small hint from me – Przemek Broda Mar 04 '20 at 20:17
  • You can get context in initState, however you can't use it for everything. See: https://stackoverflow.com/a/49458289/11709863 I wouldn't use didChangeDependencies as that can be called multiple times during the widget's lifecycle. To access a provider in initState you just have to set listen to false like so: final myState = Provider.of(context, listen:false); – Alfred Jingle Mar 07 '20 at 06:30
  • Alfred Jingle, you can use it in didChangeDependencies but you have to check does it run for the first time or not. Something like this: if (_isInit) { //do something with context; _isInit = false; } – Przemek Broda Mar 14 '20 at 20:22
  • Simple and Easy Trick – nokieng Nov 05 '20 at 08:34
  • 2
    Is there any way of getting around this if we are using a stateless widget? – Levy77 Jan 11 '21 at 07:55
-4

I think you have some things bad in your code, maybe that's not the problem but is good to correct that: first: It is not recommendable to do that job in your main file, you should have something like a Splash page to handle that. second: You should use blocs and not write your logic code on the same place at the view(UI)

Hairon Chaviano
  • 435
  • 2
  • 5
  • 5
    well, thanks for the recommendations, but you should post it like comment, because it doesn't answer the question. – i6x86 Nov 01 '19 at 20:44
-5

If you're using android studio, try if running from the terminal fix the issue. The run button attached the debug service, which then force the entire app to be rebuilt

puntodamar
  • 45
  • 6