251

I would like to be able to run functions once a Widget has finished building/loading but I am unsure how.

My current use case is to check if a user is authenticated and if not, redirect to a login view. I do not want to check before and push either the login view or the main view, it needs to happen after the main view has loaded.

Is there anything I can use to do this?

Paresh Mangukiya
  • 37,512
  • 17
  • 201
  • 182
KingLagalot
  • 2,553
  • 2
  • 8
  • 11

12 Answers12

325

You could use

https://github.com/slightfoot/flutter_after_layout

which executes a function only one time after the layout is completed. Or just look at its implementation and add it to your code :-)

Which is basically

  void initState() {
    super.initState();
    WidgetsBinding.instance
        .addPostFrameCallback((_) => yourFunction(context));
  }
Thomas
  • 7,245
  • 6
  • 26
  • 36
129

UPDATE: Flutter v1.8.4

Both mentioned codes are working now:

Working:

WidgetsBinding.instance
        .addPostFrameCallback((_) => yourFunction(context));

Working

import 'package:flutter/scheduler.dart';

SchedulerBinding.instance.addPostFrameCallback((_) => yourFunction(context));
anmol.majhail
  • 41,678
  • 12
  • 124
  • 100
  • 1
    Second one no longer works. ```NoSuchMethodError (NoSuchMethodError: The method 'addPostFrameCallback' was called on null. Receiver: null``` – Oliver Dixon Jun 02 '19 at 17:13
  • 1
    @EliaWeiss - it Depends on your use case - This is just a way to call a function on Widgets after the build. typical use will be in init() – anmol.majhail Dec 23 '19 at 06:23
60

Best ways of doing this,

1. WidgetsBinding

WidgetsBinding.instance.addPostFrameCallback((_) {
      print("WidgetsBinding");
    });

2. SchedulerBinding

SchedulerBinding.instance.addPostFrameCallback((_) {
  print("SchedulerBinding");
});

It can be called inside initState, both will be called only once after Build widgets done with rendering.

@override
  void initState() {
    // TODO: implement initState
    super.initState();
    print("initState");
    WidgetsBinding.instance.addPostFrameCallback((_) {
      print("WidgetsBinding");
    });
    SchedulerBinding.instance.addPostFrameCallback((_) {
      print("SchedulerBinding");
    });
  }

both above codes will work the same as both use the similar binding framework. For the difference find the below link.

https://medium.com/flutterworld/flutter-schedulerbinding-vs-widgetsbinding-149c71cb607f

battlecook
  • 312
  • 3
  • 16
Jitesh Mohite
  • 24,460
  • 12
  • 119
  • 117
51

There are 3 possible ways:

1) WidgetsBinding.instance.addPostFrameCallback((_) => yourFunc(context));

2) Future.delayed(Duration.zero, () => yourFunc(context));

3) Timer.run(() => yourFunc(context));

As for context, I needed it for use in Scaffold.of(context) after all my widgets were rendered.

But in my humble opinion, the best way to do it is this:

void main() async {
  WidgetsFlutterBinding.ensureInitialized(); //all widgets are rendered here
  await yourFunc();
  runApp( MyApp() );
}
Stacky
  • 810
  • 9
  • 22
mistercx
  • 635
  • 6
  • 3
  • 2
    In GetX framework in Flutter, the second way is preferred (within the widget declaration): `Future.delayed(Duration.zero, () => yourFunc(context));` – Constantine Kurbatov May 24 '21 at 17:09
  • I can confirm @ConstantineKurbatov. Using GetX and `WidgetsBinding` did not work but produced erroneous results and odd behaviour. Using `Future.delayed()` solved my problems! – Jonathan Rhein Jul 11 '21 at 06:41
  • hi, @JonathanRhein, I used the first choice exactly in a project and it didn't generate any error, could you explain more about the error happened to you? – Felipe Sales Mar 27 '22 at 02:35
14

Flutter 1.2 - dart 2.2

According with the official guidelines and sources if you want to be certain that also the last frame of your layout was drawned you can write for example:

import 'package:flutter/scheduler.dart';

void initState() {
   super.initState();
   if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
        SchedulerBinding.instance.addPostFrameCallback((_) => yourFunction(context));
   }
}
Community
  • 1
  • 1
Alessandro Ornano
  • 33,355
  • 11
  • 97
  • 127
  • For me this didn't work, because at initState() time I get schedulerPhase with _SchedulerPhase.idle_ value ... what it actually worked was to add that check within build() – Alessio Apr 24 '19 at 06:38
  • try the following way: `Widget build(BuildContext context) { Future.delayed(Duration.zero,() {//some action on complete}); return Scaffold() };` – Constantine Kurbatov May 24 '21 at 17:11
13

If you are looking for ReactNative's componentDidMount equivalent, Flutter has it. It's not that simple but it's working just the same way. In Flutter, Widgets do not handle their events directly. Instead they use their State object to do that.

class MyWidget extends StatefulWidget{

  @override
  State<StatefulWidget> createState() => MyState(this);

  Widget build(BuildContext context){...} //build layout here

  void onLoad(BuildContext context){...} //callback when layout build done
}

class MyState extends State<MyWidget>{

  MyWidget widget;

  MyState(this.widget);

  @override
  Widget build(BuildContext context) => widget.build(context);

  @override
  void initState() => widget.onLoad(context);
}

State.initState immediately will be called once upon screen has finishes rendering the layout. And will never again be called even on hot reload if you're in debug mode, until explicitly reaches time to do so.

stack underflow
  • 1,094
  • 1
  • 17
  • 44
  • From my example, you can use `StatefulWidget` class to handle it's `State` object just like a `StatelessWidget` but I highly not recommend it. I'm not yet found any issue but please try to implement everything inside `State` object first – stack underflow Feb 27 '19 at 13:45
  • 2
    https://flutter.dev/docs/cookbook/networking/fetch-data Google recommends to call data fetching on initState(). Therefore there is no issue with this solution, in fact this should be the accepted answer. – Developer Nov 02 '19 at 13:55
  • In React Native data fetching can be doing in `componentWillMount` just before layout rendering. Flutter provides simpler solution. `initState` is enough for both data fetching and on layout rendered if we know how to doing it properly – stack underflow Nov 03 '19 at 02:37
  • 1
    componentWillMount will be deprecated soon. Therefore fetching will be done after the component has been mounted and constructed. – Developer Nov 03 '19 at 09:33
13

In flutter version 1.14.6, Dart version 28.

Below is what worked for me, You simply just need to bundle everything you want to happen after the build method into a separate method or function.

@override
void initState() {
super.initState();
print('hello girl');

WidgetsBinding.instance
    .addPostFrameCallback((_) => afterLayoutWidgetBuild());

}
Uchenna Nnodim
  • 392
  • 2
  • 10
5

Try SchedulerBinding,

 SchedulerBinding.instance
                .addPostFrameCallback((_) => setState(() {
              isDataFetched = true;
            }));
Navin Kumar
  • 2,538
  • 3
  • 16
  • 42
1

The PostFrameCallback fires before the screen has fully painted. Therefore Devv's answer above was helpful with the added delay to allow the screen to paint.

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
       Future.delayed(Duration(seconds: 3), () => yourFunction());
    });
  }
Justin
  • 45
  • 8
0

If you want to do this only once, then do it because The framework will call initState() method exactly once for each State object it creates.

 @override
  void initState() {
    super.initState();
    WidgetsBinding.instance
        .addPostFrameCallback((_) => executeAfterBuildComplete(context));
  }

If you want to do this again and again like on back or navigate to a next screen and etc..., then do it because didChangeDependencies() Called when a dependency of this State object changes.

For example, if the previous call to build referenced an InheritedWidget that later changed, the framework would call this method to notify this object about the change.

This method is also called immediately after initState. It is safe to call BuildContext.dependOnInheritedWidgetOfExactType from this method.

 @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    WidgetsBinding.instance
        .addPostFrameCallback((_) => executeAfterBuildComplete(context));
  }

This is the your Callback function

executeAfterBuildComplete([BuildContext context]){
    print("Build Process Complete");
  }
Paresh Mangukiya
  • 37,512
  • 17
  • 201
  • 182
0

another solution that worked pretty well for me is wrapping the function you want to call by Future.delayed() as showen below:

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
       Future.delayed(Duration(seconds: 3), () => yourFunction());
    });
  }
Devv
  • 39
  • 3
0

my english is poor forgive me

import 'package:flutter/material.dart';

class TestBox extends StatefulWidget {
  final Color color;
  final Duration delay;

  const TestBox({
    Key? key,
    this.color = Colors.red,
    this.delay = const Duration(seconds: 5),
  }) : super(key: key);

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

class _TestBoxState extends State<TestBox> {
  String? label;

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

  void initialMembers() async {
    label = await fetchLabel();

    if (mounted) setState(() {});

    /// don't worry
    /// if `(!mounted)`, means wen `build` calld
    /// the label already has the newest value
  }

  Future<String> fetchLabel() async {
    await Future.delayed(widget.delay);
    print('fetchLabel call');
    return 'from fetchLabel()';
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedContainer(
      margin: EdgeInsets.symmetric(vertical: 12),
      duration: Duration(milliseconds: 500),
      width: 220,
      height: 120,
      color: label == null ? Colors.white : widget.color,
      child: Center(
        child: Text(label ?? 'fetching...'),
      ),
    );
  }
}

Column(
  children: [
    TestBox(
      delay: Duration(seconds: 1),
      color: Colors.green,
    ),
    TestBox(
      delay: Duration(seconds: 3),
      color: Colors.yellow,
    ),
    TestBox(
      delay: Duration(seconds: 5),
      color: Colors.red,
    ),
  ],
),
丁晓宇
  • 1
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 22 '22 at 06:48