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

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

Related

TabBarView page not rebuilding correctly

I am trying to display the tab number on each page of a TabBarView, by reading the index of its TabController. For some reason though, the value does not seem to update correctly visually, even though the correct value is printed in the logs.
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> with SingleTickerProviderStateMixin {
TabController? _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(
length: 3,
vsync: this,
);
}
_back() {
if (_tabController!.index > 0) {
_tabController!.animateTo(_tabController!.index - 1);
setState(() {});
}
}
_next() {
if (_tabController!.index < _tabController!.length - 1) {
_tabController!.animateTo(_tabController!.index + 1);
setState(() {});
}
}
Widget _tab(int index) {
var value = "Page $index: ${_tabController!.index + 1} / ${_tabController!.length}";
print(value);
return Row(
children: [
TextButton(
onPressed: _back,
child: const Text("Back"),
),
Text(value,
style: const TextStyle(
),
),
TextButton(
onPressed: _next,
child: const Text("Next"),
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: TabBarView(
controller: _tabController,
children: [
_tab(1),
_tab(2),
_tab(3),
],
)
);
}
}
When navigating from index 0 to index 1, the following is printed in the logs, as expected:
I/flutter (25730): Page 1: 2 / 3
I/flutter (25730): Page 2: 2 / 3
I/flutter (25730): Page 3: 2 / 3
However, what is actually displayed is Page 2: 1 / 3
I have tried using UniqueKey as well as calling setState on the next frame, but it doesn't make a difference. Calling setState with a hardcoded delay seems to work, but it also seems wrong.
Why is what's printed in the logs different to what's being displayed, considering that all tabs are rebuilt when setState is called? Assuming it has something to do with the PageView/Scrollable/Viewport widgets that make up the TabBarView, but what exactly is going on? Notice how even when going from page 1 to page 2 and then to page 3, none of the values on any of the pages are being updated, so even the on-screen widgets aren't rebuilding correctly.
I am finally able to answer my own question. This odd behaviour is explained by the internal logic of the _TabBarViewState. The TabBarView uses a PageView internally, which it animates based on changes to the TabController index. Here is a snippet of that logic:
final int previousIndex = _controller!.previousIndex;
if ((_currentIndex! - previousIndex).abs() == 1) {
_warpUnderwayCount += 1;
await _pageController.animateToPage(_currentIndex!, duration: kTabScrollDuration, curve: Curves.ease);
_warpUnderwayCount -= 1;
return Future<void>.value();
}
Note that it keeps track of whether an animation is in progress with the _warpUnderwayCount variable, which will get a value of 1 as soon as we call animateTo() on the TabController.
Additionally, the _TabBarViewState maintains a _children list of widgets representing each page, which is first created when the TabBarView is initialized, and can later be updated only by the _TabBarViewState itself by calling its _updateChildren() function:
void _updateChildren() {
_children = widget.children;
_childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children);
}
The _TabBarViewState also overrides the default behaviour of the didUpdateWidget function:
#override
void didUpdateWidget(TabBarView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != oldWidget.controller)
_updateTabController();
if (widget.children != oldWidget.children && _warpUnderwayCount == 0)
_updateChildren();
}
Note that even though we provide a new list of children from our parent stateful widget by calling setState() just after animateTo(), that list of children will be ignored by the TabBarView because _warpUnderwayCount will have a value of 1 at the point that didUpdateWidget is called, and therefore _updateChildren() will not be called as per the internal logic shown above.
I believe this is a constraint of the TabBarView widget that has to do with its complexity in terms of coordinating with its internal PageView as well as with an optional TabBar widget with which it shares a TabController.
In terms of a solution, given that rebuilding the whole TabBarView by updating its Key would cancel the animation, and that setting new children by calling setState() after calling animateTo() is ignored if done while the page change animation is still running, I can only think of calling setState() after saving all the variables required for rebuilding the children and before animateTo() is called on the next frame. If it is called within the same frame, the children will still not update because didUpdateWidget will still be called after the animation starts. Here is the code from my question, updated with the proposed solution:
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> with SingleTickerProviderStateMixin {
TabController? _tabController;
int _newIndex = 0;
#override
void initState() {
super.initState();
_tabController = TabController(
length: 3,
vsync: this,
);
}
_back() {
if (_tabController!.index > 0) {
_newIndex = _tabController!.index - 1;
setState(() {});
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
_tabController!.animateTo(_newIndex);
});
}
}
_next() {
if (_tabController!.index < _tabController!.length - 1) {
_newIndex = _tabController!.index + 1;
setState(() {});
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
_tabController!.animateTo(_newIndex);
});
}
}
Widget _tab(int index) {
var value = "Page $index: ${_newIndex + 1} / ${_tabController!.length}";
print(value);
return Row(
children: [
TextButton(
onPressed: _back,
child: const Text("Back"),
),
Text(value,
style: const TextStyle(
),
),
TextButton(
onPressed: _next,
child: const Text("Next"),
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: TabBarView(
controller: _tabController,
children: [
_tab(1),
_tab(2),
_tab(3),
],
)
);
}
}
You can use Stream to listen for tab index change when switching pages. Update the index when changing page.
final _tabPageIndicator = StreamController<int>.broadcast();
Stream<int> get getTabPage => _tabPageIndicator.stream;
...
// Update tab index on Stream
_tabPageIndicator.sink.add(_tabController!.index + 1);
Then using StreamBuilder, this gets rebuild when there's a change on the Stream it's listening to. There's no need to use setState() to rebuild the Widgets inside StreamBuilder.
StreamBuilder<int>(
stream: getTabPage,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData && snapshot.data != null) {
tabIndex = snapshot.data!;
}
return Text(
'Page $index: [$tabIndex / ${_tabController!.length}]',
style: const TextStyle(),
);
}
),
Complete Sample
import 'dart:async';
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> with SingleTickerProviderStateMixin {
TabController? _tabController;
int tabIndex = 1;
final _tabPageIndicator = StreamController<int>.broadcast();
Stream<int> get getTabPage => _tabPageIndicator.stream;
#override
void initState() {
super.initState();
_tabController = TabController(
length: 3,
vsync: this,
);
// Update tab index on Stream
_tabPageIndicator.sink.add(_tabController!.index + 1);
}
#override
void dispose() {
super.dispose();
// Close Stream when not in use
_tabPageIndicator.close();
}
_back() {
if (_tabController!.index > 0) {
_tabController!.animateTo(_tabController!.index - 1);
// setState(() {
// });
// Update tab index on Stream
_tabPageIndicator.sink.add(_tabController!.index + 1);
}
}
_next() {
if (_tabController!.index < _tabController!.length - 1) {
_tabController!.animateTo(_tabController!.index + 1);
// setState(() {
// });
// Update tab index on Stream
_tabPageIndicator.sink.add(_tabController!.index + 1);
}
}
Widget _tab(int index) {
var value =
"Page $index: ${_tabController!.index + 1} / ${_tabController!.length}";
debugPrint(value);
return Row(
children: [
TextButton(
onPressed: _back,
child: const Text("Back"),
),
// StreamBuilder rebuilds every time there's a change on Stream
StreamBuilder<int>(
stream: getTabPage,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData && snapshot.data != null) {
tabIndex = snapshot.data!;
}
return Text(
'Page $index: [$tabIndex / ${_tabController!.length}]',
style: const TextStyle(),
);
}),
TextButton(
onPressed: _next,
child: const Text("Next"),
),
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: TabBarView(
controller: _tabController,
children: [
_tab(1),
_tab(2),
_tab(3),
],
));
}
}
From documentation of flutter animatedTo method description: animatedTo immediately sets index and previous index and then plays the animation from its current value to index.
Once the _tab method is called, the widget that returns from it is now in the widget tree.
Whenever the build method is run again then its appearance will change.
Every time the _tab method is called, the part of the code that does not return the widget runs again, and return widget which is already in the widget tree.
But it is necessary to run the build method again for the widget to change.
The build is called when the widget is built for the first time. But after that, it is necessary to re-run the build method with setState.
I convert your tab navigation buttons to Widget class. We can more easily understand the comparison with the _tab method and Widget class.
When navigating from index 0 to index 1,2,3 the following is printed in the logs:
flutter: Page 0: 1 / 3
flutter: Page 0: 1 / 3
import 'package:flutter/material.dart';
void main() {
runApp(const TestMyApp());
}
class TestMyApp extends StatelessWidget {
const TestMyApp({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>
with SingleTickerProviderStateMixin {
TabController? _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(
length: 3,
vsync: this,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_tab(1),
_tab(2),
_tab(3),
],
),
),
Expanded(
child: TabNavigationWidget(tabController: _tabController!)),
],
));
}
Widget _tab(int index) {
return Text('$index');
}
}
class TabNavigationWidget extends StatelessWidget {
TabController tabController;
TabNavigationWidget({Key? key, required this.tabController})
: super(key: key);
#override
Widget build(BuildContext context) {
var value = "Page : ${tabController.index + 1} / ${tabController.length}";
print(value);
return Row(
children: [
TextButton(
onPressed: _back,
child: Text("Back ${tabController.index}"),
),
Text(
value,
style: const TextStyle(),
),
TextButton(
onPressed: _next,
child: const Text("Next"),
),
],
);
}
_back() {
if (tabController.index > 0) {
tabController.animateTo(tabController.index - 1);
}
}
_next() {
if (tabController.index < tabController.length - 1) {
tabController.animateTo(tabController.index + 1);
}
}
}
I recommend to you use widgets classes instead of _tab() methods. Your _tab methods build 3 times when the setState method is called.
I know too little about widget tree feel free to correct and update the answer.
All tabs are building initially, while the _tabController!.index is 0. _next method does to wait for animateTo to finish the animation, then call setState. Using setState rebuild the UI under build but the TabBarView is not rebuilding until we are telling it that it is having changes.
widget tree is smart enough while updating the UI. -🔎
While creating a widget, without providing key it generates objectRuntimeType key, and doesn't change(same for providing key) on calling setState.
While here, update is depending on key, and widget tree(key) is not different for TabBarView and TabBarView is thinking nothing happen to me, we can't see any update on UI.
Then next comes by adding listener
Register a closure to be called when the object changes.
We can add listener on TabController to listen changes and inside setState to update the UI. You can also remove setState from _back and _next methods.
_tabController = TabController(
length: 3,
vsync: this,
)..addListener(() {
setState(() {});
});
Or just
Use index instead of _tabController!.index while both responsibility is same inside Row.

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 can I make the initial value of a TextFormField equal to a variable loaded from shared preferences? (Flutter)

I want to make the initial value of the TextFormField equal to the counter variable. The counter is maintained between app restarts, but when I restart the app, the initial value of the text field is always 0.
Is there a better way of doing that?
(I'm new to programming, sorry if it's a dumb question)
Here's the code I used.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#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, 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
_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0);
});
}
//Incrementing counter after click
_incrementCounter() async {
SharedPreferences 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.spaceEvenly,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
TextFormField(
initialValue: '$_counter',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Try adding a controller for TextFormField and update the value after getting it from SharedPreferences.
Something like this.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#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, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
int _counter = 0;
final myController = TextEditingController();
#override
void dispose() {
myController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_loadCounter();
}
_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0);
myController.text = _counter.toString();
});
}
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0) + 1;
prefs.setInt('counter', _counter);
myController.text = _counter.toString();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
TextFormField(
controller: myController,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Hope this solves your issue.

didUpdateWidget called immediately after initState in 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),
),
);
}
}

Async Listview update in flutter

I have created a program which displays a list view in build method and in init I have a async method.
That async method after 3 seconds adds an element in list and try to set State.
It is not working. My code is as follows.
calling async function in init may be wrong, i want to show the List view then make an async http call and then update the list view. and this should work even after push and pop.
import 'dart:async';
import 'package:flutter/material.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, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> europeanCountries = [
'Albania',
'Andorra',
'Armenia',
'Austria',
'Azerbaijan',
'Belarus',
'Belgium',
'Bosnia and Herzegovina'
];
int _counter = 0;
void _incrementCounter() async {
const ThreeSec = const Duration(seconds: 3);
Timer(ThreeSec, () {
europeanCountries.insert(0, "Testing");
print(europeanCountries);
});
setState(() {
_counter++;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
_incrementCounter();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _myListView(context),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Widget _myListView(BuildContext context) {
// backing data
return ListView.builder(
itemCount: europeanCountries.length,
reverse: true,
itemBuilder: (context, index) {
return ListTile(
title: Text(europeanCountries[index]),
);
},
);
}
}
Use timeout method handle and call setState method inside that method like following way
Timer(ThreeSec, () {
europeanCountries.insert(0, "Testing");
print(europeanCountries);
setState(() {
_counter++;
});
});