Reputation: 3662
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?
Upvotes: 361
Views: 378533
Reputation: 500
For GetX using SchedulerBinding
instead of WidgetsBinding
did the job
SchedulerBinding.instance.addPostFrameCallback((_) {
// your code here
});
Upvotes: 3
Reputation: 34270
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.
Upvotes: 111
Reputation: 268314
If you don't want to use WidgetsBinding
or SchedulerBinding
:
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();
}
Future<void> _runsAfterBuild() async {
await Future((){}); // <-- Dummy await
// This code runs after build ...
}
@override
Widget build(BuildContext context) {
_runsAfterBuild();
return Container();
}
Upvotes: 6
Reputation: 9135
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
};
},
);
Upvotes: 7
Reputation: 391
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 ..."
Upvotes: -1
Reputation: 165
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());
});
}
Upvotes: 9
Reputation: 21
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,
),
],
),
Upvotes: 1
Reputation: 45
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());
});
}
Upvotes: -2
Reputation: 821
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() );
}
Upvotes: 69
Reputation: 484
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());
}
Upvotes: 14
Reputation: 4027
Try SchedulerBinding,
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isDataFetched = true;
}));
Upvotes: 5
Reputation: 51316
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));
Upvotes: 167
Reputation: 35402
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));
}
}
Upvotes: 14
Reputation: 1774
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, Widget
s 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.
Upvotes: 14
Reputation: 9277
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));
}
Upvotes: 435