364

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?

3
  • 3
    It's unlikely that you want to start the login process in build. Build can be called at any time multiple times. Commented Mar 24, 2018 at 15:45
  • Look at this: stackoverflow.com/questions/49359706/redirect-on-app-load Commented Mar 24, 2018 at 16:22
  • In my case, I want the splash screen to stay on until I have decided whether or not the user is logged in so I can take them past the login screen. That has proven very difficult, since it's very hard to decide whether the next screen after the login screen has finished being rendered or not!... Commented Jul 14, 2023 at 16:54

15 Answers 15

441

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));
  }
Sign up to request clarification or add additional context in comments.

9 Comments

See @anmol.majhail answer: WidgetsBinding.instance.addPostFrameCallback((_) => yourFunciton(context)); is not longer working
@anunixercoder : it depends on your use case. At times, you should call it in other than initState, eg. in build.
you should call setState within yourFunction method to make it working
Is using WidgetsBinding.instance.addPostFrameCallback a lot of times indicate bad practise in code?
@Thomas, This solution worked for me without any problem. thanks a lot.
|
168

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));

4 Comments

Second one no longer works. NoSuchMethodError (NoSuchMethodError: The method 'addPostFrameCallback' was called on null. Receiver: null
@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()
I am trying to call a function from initState that uses values from localStorage. I call WidgetsBinding.instance .addPostFrameCallback((_) => _authenticateWithLocalStorage()); I get an binding error while localstorage is being initialized.
The context captured from the calling scope may be invalid (defunct) by the time the post-frame callback is called, which can lead to an exception (e.g. if you try to access context.state, when state has been nulled out after layout).
111

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.

3 Comments

what if, if it is a StatelessWidget?
This makes sense only for StatefulWidgets, as StatelessWidgets can't re-render themselves.
"can't re-render" And? Still has one initial render, hence, for StatelessWidget call addPostFrameCallback from within the build method ;-)
70

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() );
}

3 Comments

In GetX framework in Flutter, the second way is preferred (within the widget declaration): Future.delayed(Duration.zero, () => yourFunc(context));
I can confirm @ConstantineKurbatov. Using GetX and WidgetsBinding did not work but produced erroneous results and odd behaviour. Using Future.delayed() solved my problems!
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?
14

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.

5 Comments

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
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.
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
componentWillMount will be deprecated soon. Therefore fetching will be done after the component has been mounted and constructed.
This looks like a highly unconventional way to do it!... Why would you put the build method of the state inside the widget head, rather than inside the state, as normal??...
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));
   }
}

3 Comments

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()
try the following way: Widget build(BuildContext context) { Future.delayed(Duration.zero,() {//some action on complete}); return Scaffold() };
@ConstantineKurbatov solution would work, but not as expected, since every build (ex: setstate) your function will be called, so it will be called multiple times
14

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());

}

Comments

9

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());
    });
  }

1 Comment

The problem with adding for example 3 sec is that you don't actually know if it takes 3 sec! It may also differ on different devices!... There's gotta be a way to let the program itself tell you when it's done painting.
7

if you having issue with new SDK and old answer you can try my solution.I have tested it on v3.0.4

WidgetsBinding.instance.endOfFrame.then(
  (_) {
    if (mounted) {
          // do some suff 
          // you can get width height of specific widget based on GlobalKey
       };
  },
);

Comments

6

If you don't want to use WidgetsBinding or SchedulerBinding:

  • Use Future or Timer (easy-peasy)

    Future<void> _runsAfterBuild() async {
      // This code runs after build ...
    }
    
    @override
    Widget build(BuildContext context) {
      Future(_runsAfterBuild); // <-- Use Future or Timer
      return Container();
    }
    
  • Await a dummy Future

    Future<void> _runsAfterBuild() async {
      await Future((){}); // <-- Dummy await
    
      // This code runs after build ...
    }
    
    @override
    Widget build(BuildContext context) {
      _runsAfterBuild();
      return Container();
    }
    

3 Comments

what does this line mean , or how it works ? ` await Future((){}); // <-- Dummy await`
It works because, so far, asynchronous code is always scheduled to run after synchronous code, so build() (sync method) will run in its entirety before _runsAfterBuild() (async method) even starts running.
This code still works correctly whether your function is StateLess or StateFull 👍.
5

Try SchedulerBinding,

 SchedulerBinding.instance
                .addPostFrameCallback((_) => setState(() {
              isDataFetched = true;
            }));

Comments

2

For GetX using SchedulerBinding instead of WidgetsBinding did the job

SchedulerBinding.instance.addPostFrameCallback((_) {
  // your code here
});

Comments

1

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 Comment

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.
-1

I have a Stateful widget where I use html_editor_enhanced plugin widget. This is the only way to set initial message in it.

class _SendChatMessageState extends State<SendChatMessage> {
  final _htmlController = HtmlEditorController();

      @override
      void initState() {
        super.initState();
    
          Future.delayed(const Duration(seconds: 3), () {
            _htmlController.setText(widget.chatMessage.message ?? '');
          });
      }

I tried addPostFrameCallback but it didn't work because a JavaScript generates exception "HTML editor is still loading, please wait before evaluating this JS ..."

Comments

-2

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());
    });
  }

2 Comments

Why would you add a delay after the widget has been built?
The delay is completely useless here, beside even if you haven't used WidgetsBinding.instance.addPostFrameCallback, 3 seconds is too much.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.