didUpdateWidget called immediately after initState in Flutter - flutter

According to Flutter Documentation:
didUpdateWidget called whenever the widget configuration changes
But, in the following code, didUpdateWidget is called immediately after initState on the first time.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
#override
void initState() {
print("initState called");
super.initState();
}
#override
void didUpdateWidget(Test oldWidget) {
print("didUpdateWidget called");
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return Container();
}
}
// output
//
// initState called
// didUpdateWidget called
Can someone describe why this happens? and how can I compare the whole oldWidget with widget
Thank you
update
as #pskink mentioned, didUpdateWidget is not called immediately after initState, it's after the first build
Yet another question is why it is called after the first build with the following code:
print("didUpdateWidget called"); <--
super.didUpdateWidget(oldWidget); <--
but if I call print after super.didUpdateWidget(oldWidget);, it works fine.

In my test using Flutter 2.10.1 stable version, didUpdateWidget was only called after calling setState() to update the data displayed on the Widget. This behavior matches the description mentioned on the docs where the didUpdateWidget method is called whenever the widget configuration changes. After the didUpdateWidget method was called, build will then be called.
Here's how the flow can look like after calling setState() once.
initState()
build()
setState() called
didUpdateWidget()
build()
This can be replicated on this simple Flutter app.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
void initState() {
debugPrint('State initState');
super.initState();
}
#override
void didUpdateWidget(MyHomePage oldWidget) {
debugPrint('State didUpdateWidget');
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
debugPrint('State Widget build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}

Related

Flutter mocked clock (using withClock) behave inconsistenly when app starts with WidgetsFlutterBinding.ensureInitialized()

If flutter app starts with:
void main() {
WidgetsFlutterBinding.ensureInitialized();
Then clock.now() prints not mocked value if outside of build method.
Since I am using withClock in my tests, it makes my app unable to be properly tested as sometimes clock.now() returns mocked value, and sometimes real current time.
Please find the following example, and note that _printClockNow prints DateTime.now(), but it should print year 1990.
If you removed line WidgetsFlutterBinding.ensureInitialized() then it works as expected.
Minimum reproduceable code:
import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
withClock(Clock.fixed(DateTime(1990)), () {
runApp(const MyApp());
});
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void _printClockNow() {
print('_printClockNow is: ${clock.now()}'); // prints real NOW
setState(() {});
}
#override
Widget build(BuildContext context) {
print('build clock.now() is: ${clock.now()}'); // prints mocked NOW
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Dummy()],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _printClockNow,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class Dummy extends StatelessWidget {
#override
Widget build(Object context) {
return Text('t: ${clock.now()}');
}
}
Try with putting WidgetsFlutterBinding inside withClock.
void main() {
withClock(Clock.fixed(DateTime(1990)), () {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
});
}

Non-nullable instance field ['controller'] must be initialized in flutter

Good day!
I am a new to Flutter to would like to start my Startup but I was trapped in this problem to make Tabbar example...
I have almost been frustrated all day because of this problems....
Dart Analysis keeps saying this below....
Non-nullable instance field ['controller'] must be initialized in flutter
really appreciate in advance.
import 'package:flutter/material.dart';
import 'sub/firstPage.dart';
import 'sub/secondPage.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
TabController controller;
#override
void initState() {
super.initState();
controller = TabController(length: 2, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TabBar Example'),
),
body: TabBarView(
children: <Widget>[FirstApp(), SecondApp()],
controller: controller,
),
bottomNavigationBar: TabBar(tabs: [
Tab(icon: Icon(Icons.looks_one, color: Colors.blue),),
Tab(icon: Icon(Icons.looks_two, color: Colors.blue),),
],
)
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
Plase try, you missed use controller
import 'package:flutter/material.dart';
import 'sub/firstPage.dart';
import 'sub/secondPage.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late TabController controller;
#override
void initState() {
super.initState();
controller = TabController(length: 2, vsync: this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TabBar Example'),
),
body: TabBarView(
children: <Widget>[FirstApp(), SecondApp()],
controller: controller,
),
bottomNavigationBar: TabBar(
controller: controller, // add here
tabs: [
Tab(icon: Icon(Icons.looks_one, color: Colors.blue),),
Tab(icon: Icon(Icons.looks_two, color: Colors.blue),),
],
)
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
Since your field does not allow null values, but you cannot initialize your field when it's created, you have to declare it as late:
late TabController controller;
This allows you to assign a value later, but you will get an exception if you use it before it was assigned. The way your code looks, it should be okay.
It looks like you are using null safety in your project. So the dart is complaining about this line. Dart expects it to be initialized right after it was created:
TabController controller;
To fix the problem add the "late" keyword before TabController:
late TabController controller;

Please explain what #override, initState, super.initState means in simple terms

I browsed these words on internet but I couldn't find a simple explanation.
#override
void initState() {
super.initState();
_loadCounter();
}
I was going through a example code on flutter cookbook > Persistence >Store key-value data on disk https://flutter.dev/docs/cookbook/persistence/key-value
Complete code:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of the application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shared preferences demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Shared preferences demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
#override
void initState() {
super.initState();
_loadCounter();
}
//Loading counter value on start
void _loadCounter() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0);
});
}
//Incrementing counter after click
void _incrementCounter() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0) + 1;
prefs.setInt('counter', _counter);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
#override
points out that the function is also defined in an ancestor class, but is being redefined to do something else in the current class. It is optional to use but recommended as it improves readability.
super.initState()
forwards to the default implementation of the State base class of your widget. If you don't override, the default implementation will not be executed but the widget depends on that to work properly.
initState
is called when the widget is inserted in widget tree... it means whenever the widget with is called it would run initState function at first.... it would be called only once and if you reState that widget it won't be called again

Flutter MaterialApp with navigatorKey and key is not restarting

When testing, press the increment button a few times to view if the app restarts.
I was trying to "restart" a toy app in Flutter. I was using the old counter example and after modifying it the behavior is not the expected.
For example, with this code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
UniqueKey _materialKey;
UniqueKey _homeKey;
#override
void initState() {
super.initState();
_awaitAndRun();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
key: _materialKey,
navigatorKey: _navigatorKey,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(
key: _homeKey,
title: 'Flutter Demo Home Page',
),
);
}
Future<void> _awaitAndRun() async {
print('Starting delay... Please press the button before the time ends.');
await Future<dynamic>.delayed(Duration(seconds: 5));
setState(() {
_materialKey = UniqueKey();
_homeKey = UniqueKey();
});
print('Has been the screen reloaded?');
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
The awaitAndRun function should restart the App because I am setting the _materialKey and _homeKey as new instances. As part of it, the MyHomePage widget also should be rebuilt (because the key value has changed) but it didn't.
I can understand it's because the _navigatorKey is "saving" the state of the MaterialApp but the MyHomePage should be rebuild because its key has changed!
It's even more strange because if I remove the _materialKey (as shown in the code below), the MyHomePage widget gets rebuilt.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
UniqueKey _materialKey;
UniqueKey _homeKey;
#override
void initState() {
super.initState();
_awaitAndRun();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
//key: _materialKey,
navigatorKey: _navigatorKey,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(
key: _homeKey,
title: 'Flutter Demo Home Page',
),
);
}
Future<void> _awaitAndRun() async {
print('Starting delay... Please press the button before the time ends.');
await Future<dynamic>.delayed(Duration(seconds: 5));
setState(() {
_materialKey = UniqueKey();
_homeKey = UniqueKey();
});
print('Has been the screen reloaded?');
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
So it doesn't have any sense for me, why does this happen? Why when I set the _materialKey the MyHomePage widget doesn't get rebuilt?
It's pretty normal. Flutter always target safe and clean state and doesn't keep any item usually time. You can use a lot of methods, so I think should provider
You need to game states keep to app state, so I created ToysGame(sample) and added count property, you can use and change this property so unaffected state changes.
Firstly wrap single provider instance to your app page.
home: Provider(
create: (context) => ToysGame(),
child: MyHomePage(
key: _homeKey,
title: 'Flutter Demo Home Page',
),
),
After doesn't keep count on in page state, you should use toys state with provider.
Text(
Provider.of<ToysGame>(context).count.toString(),
style: Theme.of(context).textTheme.headline4,
),
And you want to change value
void _incrementCounter() {
Provider.of<ToysGame>(context, listen: false).count++;
setState(() {});
}
Finally, it's done. I added on my stackhelpover repo with restart app lib name.
(You read provider package and maybe other feature help for you [ change notifier or multi-provider ])
StackHelpOver

How to call method in state class in flutter?

I want to call the method from HomeState class to _MyHomePageState class.But i have no idea to do that.
this is main.dart :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
Home home = new Home();
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){}
),
);
}
}
Home class:
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return HomeState();
}
}
class HomeState extends State<Home> {
int numberPrint(){
setState((){});
}
#override
Widget build(BuildContext context) {
}
}
I want to call the method numberPrint() in floatingbutton in _MyHomePageState class in main.dart.
Please help me to do that.
Please take a look at how I would have handled, this is not the best, but it would give you a better idea.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
//MyHomePage---------------------------------------------------
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
MyController controller = MyController();
#override
Widget build(BuildContext context) {
Home home = new Home(controller:controller);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: controller.execute,
),
);
}
}
//Home---------------------------------------------------------------------
class Home extends StatefulWidget {
Home({this.controller});
final MyController controller;
#override
State<StatefulWidget> createState() {
return HomeState();
}
}
class HomeState extends State<Home> {
initState(){
widget.controller.addListener(numberPrint);
}
numberPrint(){
setState((){});
}
#override
Widget build(BuildContext context) {
}
}
//MyController--------------------------------------------------------
class MyController{
Function listener;
addListener(Function fn){
listener = fn;
}
execute(){
listener();
}
}
In general, I use some kind of page nerve center through which widgets can register their setState functions or other things such as FocusNode and even data.
For example,
class PageNerveCenter{
Function requireHomeRebuild;
}
PageNerveCenter _pageNerveCenter = PageNerveCenter();
class Home extends StatefulWidget{...}
class _HomeState extends State<Home>{
...
#override
initState(){
super.initState();
// register setState
_pageNerveCenter.requireHomeRebuild = (){
setState((){})
};
}
#override
dispose(){
// remove setState when object is disposed
_pageNerveCenter.requireHomeRebuild = (){};
}
...
}
In some other widget (same file/library),
_pageNerveCenter.requireHomeRebuild();
This approach then allows you to call a widget's setState from anywhere in the file, from inside another widget.
In my own experience, this approach has helped to easily break down my widget tree and only rebuild those widgets that actually need to rebuilt too.
I would certainly like to hear other more experienced flutter developers about their take on this approach since I am relatively new too.