Flutter, Google’s UI toolkit, has been making waves in cross-platform app development by enabling developers to create aesthetically pleasing, high-performance apps for various platforms with a single codebase. But, as a newbie or even an experienced developer, you might sometimes find yourself tangled up in creating and managing the state of the apps. This state management practically finds its foundation in initState - a super-powerful method Flutter provides.
This blog offers readers a knowledge-packed, deep-dive tour of initState, what it does, and how to effectively use it when developing Flutter apps.
Understanding and properly using initState are crucial for any Flutter developer. initState, as an integral part of the Flutter framework, is like a car's ignition key - it starts the engine and sets the wheels of the app rolling. It is primarily called immediately when an object of a stateful widget is inserted into the widget tree, handling the initial setup. How does it do that? Let's dive into that in the next section.
The initState method in Flutter is a part of the state class that comes into play as soon as the stateful widget has been inserted into the widget tree. As per official Flutter documentation, 'initState' is a method called immediately when the framework inserts the state object. It's a method inside the state class. It is marked with @protected and @mustCallSuper, implying that it should be overridden carefully in your custom subclasses, with a call to the inherited method (super.initState()) at the beginning.
Being part of the StatefulWidget lifecycle, initState is indispensable when you want to initialize data or perform a setup that requires a build context. The Flutter initState method is where you will typically fetch data, initialize variables, and establish any needed connections to change notifiers, streams, or any other resources that will alter the state of your widget.
Among many types of widgets, a stateful widget has a mutable state. The initState method is a function called when the stateful widget is created and ready to be displayed to the user. For example, initState is the right place to start a method to fetch data from a server or a database, initialize variables, or even subscribe to a stream, which will be explained later in this blog. It's worth noting that after initState is called, the framework will call the build(BuildContext context) method, which will render our widget to the screen.
The Flutter widget tree is the backbone of a Flutter app and is the structural representation of widgets in the application. The initState method is intertwined with this widget tree formation in a basic yet significant way.
When a stateful widget is first inserted into the tree, the framework creates the state object, and initState is immediately called. This happens only once for each object instance in the entire lifetime of the widget. Any state management or data initialization needed by the widget happens inside the initState method.
Notably, the widget tree determines the execution order for lifecycle methods. As the initState method is the first to be executed after a stateful widget is inserted, it becomes the starting point of the widget's lifecycle. However, it's important to remember that the build context is not fully available in initState because the widget has yet to be added to the widget tree. Therefore, accessing inherited widgets here would not make sense, as there might not be a valid context to access the data.
As we have highlighted, the initState method in Flutter is a key factor in managing the state of widgets. It kickstarts the lifecycle of stateful widgets and prepares the ground for further interaction and manipulation of their state. Now, let's get a closer look at what initState does and some of the key features it offers:
A stateful widget in Flutter is a widget that describes part of the user interface which can change over time or when some interaction takes place. Think of checkboxes, forms, or any other widgets that may respond to user inputs or change due to external factors such as fetching data in real time from a server.
Now, how does initState enter into the picture here? Well, when creating a Stateful widget in Flutter, the class created has two main methods, createState(), and the "State" object itself.
When the Flutter engine decides to build a stateful widget, it first calls createState(). This creates a new State object. Then the initState method gets called on this newly created State object.
Unlike Stateless widgets, the stateful widget has a longer lifespan and can be modified over time using the setState method. To use any Stateful widget, two classes are created. First is the Widget class and the other is the State class. To initialize data in the Stateful Widget we use the initState method in the State class.
Here's an example:
1class Example extends StatefulWidget { 2 @override 3 _ExampleState createState() => _ExampleState(); 4} 5 6class _ExampleState extends State<Example> { 7 8 @override 9 void initState() { 10 super.initState(); 11 // Initialization code here 12 } 13 14 @override 15 Widget build(BuildContext context) { 16 return Container(); 17 } 18}
In the function initState, we can add the initialization code for whatever we need in our widget, like API calls, listeners, controllers, and more.
If you've ever tried to call an asynchronous function inside initState, you probably stumbled upon an interesting aspect of the Flutter framework. Flutter doesn't natively support asynchronous operations within the initState method. However, this doesn't mean having asynchronous behavior inside initState is impossible or wrong.
Occasionally, there may be a need for asynchronous operations to be executed in initState, such as API calls to fetch data during the initialization phase of a widget, or any other I/O operations. The key here is understanding how to implement asynchronous logic inside initState correctly.
Here is an example of how you can correctly execute an async function inside initState:
1class _MyHomePageState extends State<MyHomePage> { 2 late Future<String> data; 3 4 @override 5 void initState() { 6 super.initState(); 7 data = fetchData(); 8 } 9 10 Future<String> fetchData() async { 11 await Future.delayed(const Duration(seconds: 2)); 12 return 'Fetched Data'; 13 } 14 15 @override 16 Widget build(BuildContext context) { 17 return Scaffold( 18 ... 19 body: FutureBuilder<String>( 20 future: data, 21 builder: (BuildContext context, AsyncSnapshot<String> snapshot) { 22 if (snapshot.connectionState == ConnectionState.waiting) { 23 return const CircularProgressIndicator(); 24 } else { 25 if (snapshot.hasError) 26 return Text('Error: ${snapshot.error}'); 27 else 28 return Text('Data: ${snapshot.data}'); 29 } 30 }, 31 ), 32 ), 33 ); 34 } 35}
In the example above, we define an async function fetchData that simulates a delay (like the latency of an API call) and then returns a string. This function is called in initState and the result is stored in the data variable. Now, in our build function, we use FutureBuilder which takes the data as a future, and based on the future's state, returns a widget.
One of the most important considerations when using Flutter, particularly the initState method, is understanding the difference between Stateless and Stateful Widgets.
By definition, a Stateless Widget in Flutter is a widget that describes part of the user interface that can't change over time. Examples include an icon or a label - once we set the parameters of these widgets, they remain static and don't change over time or with the user's interaction.
Since Stateless widgets don't have to change state, they don't have an initState method. Stateless widgets are immovable in their properties. They take in configurations via their constructor and store them in final properties.
Here's a basic example of a Stateless Widget:
1class CustomText extends StatelessWidget { 2 final String textToDisplay; 3 4 CustomText({required this.textToDisplay}); 5 6 @override 7 Widget build(BuildContext context) { 8 return Text(textToDisplay); 9 } 10}
In this case, the CustomText StatelessWidget takes a single parameter, textToDisplay, and outputs a text widget with the provided text. It remains the same as long as the widget remains in the widget tree.
As we've covered the theory of Flutter's initState method, let's dive into some practical examples. It will help you better understand how initState can be implemented within your Flutter applications.
Here we initialize a String value in initState:
1class _MyHomePageState extends State<MyHomePage> { 2 late String welcomeText; 3 4 @override 5 void initState() { 6 super.initState(); 7 welcomeText = 'Welcome to my app!'; 8 } 9 10 @override 11 Widget build(BuildContext context) { 12 return Scaffold( 13 body: Center( 14 child: Text(welcomeText), 15 ), 16 ); 17 } 18}
In the code snippet above, a string 'welcomeText' is initialized in initState and is then used in the build method to display a welcome message inside a Text widget.
In some cases, you may require data from a server when your application starts. In this case, initState method can come in handy:
1class _MyHomePageState extends State<MyHomePage> { 2 late Future<String> data; 3 4 @override 5 void initState() { 6 super.initState(); 7 data = fetchDataFromServer(); 8 } 9 10 Future<String> fetchDataFromServer() async { 11 final response = await http.get(Uri.parse('https://api.example.com/data')); 12 13 if (response.statusCode == 200) { 14 return response.body; 15 } else { 16 throw Exception('Failed to load data from server.'); 17 } 18 } 19 20 @override 21 Widget build(BuildContext context) { 22 return Scaffold( 23 body: FutureBuilder<String>( 24 future: data, 25 builder: (BuildContext context, AsyncSnapshot<String> snapshot) { 26 if (snapshot.connectionState == ConnectionState.waiting) { 27 return CircularProgressIndicator(); 28 } else { 29 if (snapshot.hasError) 30 return Text('Error: ${snapshot.error}'); 31 else 32 return Text('Fetched data: ${snapshot.data}'); 33 } 34 }), 35 ); 36 } 37}
This code fetches data from a server during widget initialization and utilizes the FutureBuilder widget to build the UI based on the state of the Future.
These examples give you a better understanding of initState and its usage in Flutter Widgets.
Through exploring Flutter's initState method, we've traversed the basic concepts and detailed examples to understand this function's significance. As developers, understanding and properly utilizing the initState function will empower us to effectively manage the state in our applications and harness the full power of Flutter's state management system. The road ahead may still have complex state management libraries and patterns, but with initState as our starting point, we're well-equipped and have a solid foundation to build upon.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.