State management in Flutter
State management is at the heart of how Flutter apps work. It’s what lets your app respond to user interactions, update the UI, and behave dynamically — all while staying efficient and smooth. To put it simply, "state" refers to the current situation or data of your app — like what's showing on the screen, whether a button is clicked, or what items are in the shopping cart. As users interact with the app, this state changes, and Flutter makes it easy to handle these changes.
there are two conceptual types of state in any Flutter app. Ephemeral state can be implemented using State and setState() , and is often local to a single widget. The rest is your app state.
State
State refers to the current data or configuration of an application or widget at a given point in time.
-
Ephemeral State (also called Local State):
- It’s temporary and local.
- Used for things like:
- Whether a checkbox is checked
- The current value of a text field
- Which tab is selected
- Example: The state of an animation, the selected tab in a navigation bar.
- Managed using StatefulWidget and setState().
This is short-lived and used only within a single widget. Think of it like a widget's private memory — it's just for that widget and doesn't affect the rest of the app.
-
2. App State (also called Global State):
- Long-lived and shared across multiple widgets or screens.
- Example: User login data, shopping cart content.
- Managed using tools like Provider, Riverpod, or Bloc.
This is the state that’s shared across your entire app. It needs to persist and be available in multiple places — like user login data or the contents of a shopping cart.
Counter App with Ephemeral State
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CounterScreen(),
);
}
}
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _counter = 0; // Ephemeral state
void _incrementCounter() {
setState(() {
_counter++; // Update state
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Ephemeral State Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('You have pressed the button this many times:'),
Text('$_counter', style: TextStyle(fontSize: 24)),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
);
}
}
Flutter Provider Example
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Define AppState
class AppState extends ChangeNotifier {
String _userName = "Guest";
bool _isLoggedIn = false;
String get userName => _userName;
bool get isLoggedIn => _isLoggedIn;
void login(String name) {
_userName = name;
_isLoggedIn = true;
notifyListeners();
}
void logout() {
_userName = "Guest";
_isLoggedIn = false;
notifyListeners();
}
}
// Main App
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => AppState(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Consumer<AppState>(
builder: (context, appState, _) {
return appState.isLoggedIn ? HomePage() : LoginPage();
},
),
);
}
}
// Login Page
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appState = Provider.of<AppState>(context, listen: false);
return Scaffold(
appBar: AppBar(title: Text("Login")),
body: Center(
child: ElevatedButton(
onPressed: () {
appState.login("John Doe");
},
child: Text("Log In"),
),
),
);
}
}
// Home Page
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final appState = Provider.of<AppState>(context);
return Scaffold(
appBar: AppBar(
title: Text("Welcome, ${appState.userName}"),
actions: [
IconButton(
icon: Icon(Icons.logout),
onPressed: () {
appState.logout();
},
)
],
),
body: Center(child: Text("You are logged in!")),
);
}
}
Navigation and routing in Flutter
In Flutter, navigation simply means moving from one screen to another in your app — like going from a home screen to a settings screen. Routing is the system that tells Flutter which screen to show, and how to switch between them. Think of navigation as the journey, and routing as the map that guides the way.
What is Routing?
Routing helps Flutter decide what screen to show when a certain action happens — like a button press or a URL change. It's like saying, “If the user taps this, show that screen.”
Flutter makes this easy using a special class called MaterialPageRoute, and two key methods: Navigator.push Navigator.pop
MaterialPageRoute
MaterialPageRoute is a built-in Flutter class that lets you load a new screen with a smooth animation (like a slide effect). It follows the look-and-feel of the platform (Android or iOS) so your app feels natural to users.
MaterialPageRoute(builder: (context) => Widget())
Navigation.push
When you want to go to a new screen, you use Navigator.push. For example, tapping a product in a shopping app might push the product detail screen. This new screen is added on top of the previous one (like stacking cards), and users can still go back..
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewScreen()),
);
Navigator.pop
When you want to go back to the previous screen, use Navigator.pop.It removes the current screen from the top of the stack and shows the one underneath — just like flipping back a card.
Navigator.pop(
context,
);
To organize the product information efficiently, you can create a Product class in Flutter. This class will store the product details like name, price, image, etc., and can be used to pass the product information between different screens.
class Product {
final String name;
final double price;
final String image;
Product({
required this.name,
required this.price,
required this.image,
});
}
List<Product> products = [
Product(
name: 'Floppy Disk',
price: 10.99,
image: 'assets/appimages/floppy.png',
),
Product(
name: 'iPhone',
price: 999.99,
image: 'assets/appimages/iphone.png',
),
Product(
name: 'Laptop',
price: 799.99,
image: 'assets/appimages/laptop.png',
),
Product(
name: 'Pen Drive',
price: 19.99,
image: 'assets/appimages/pendrive.png',
),
Product(
name: 'Pixel Phone',
price: 899.99,
image: 'assets/appimages/pixel.png',
),
Product(
name: 'Tablet',
price: 349.99,
image: 'assets/appimages/tablet.png',
),
];
class StarRating extends StatefulWidget {
@override
_StarRatingState createState() => _StarRatingState();
}
class _StarRatingState extends State<StarRating> {
int _currentRating = 0;
void _rateOne() {
setState(() {
_currentRating = 1;
});
}
void _rateTwo() {
setState(() {
_currentRating = 2;
});
}
void _rateThree() {
setState(() {
_currentRating = 3;
});
}
@override
Widget build(BuildContext context) {
double starSize = 22;
print(_currentRating);
return Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_currentRating >= 1
? Icon(
Icons.star,
size: starSize,
)
: Icon(
Icons.star_border,
size: starSize,
)),
color: Colors.orange[600],
onPressed: _rateOne,
iconSize: starSize,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_currentRating >= 2
? Icon(
Icons.star,
size: starSize,
)
: Icon(
Icons.star_border,
size: starSize,
)),
color: Colors.orange[600],
onPressed: _rateTwo,
iconSize: starSize,
),
),
Container(
padding: EdgeInsets.all(0),
child: IconButton(
icon: (_currentRating >= 3
? Icon(
Icons.star,
size: starSize,
)
: Icon(
Icons.star_border,
size: starSize,
)),
color: Colors.orange[600],
onPressed: _rateThree,
iconSize: starSize,
),
),
],
);
}
}
class ProductCard extends StatelessWidget {
ProductCard({Key? key, required this.product}) : super(key: key);
final Product product;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(4),
height: 150,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Image.asset("assets/appimages/" + this.product.image),
Expanded(
child: Container(
padding: EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
this.product.name,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(this.product.description),
Text("Price: \$" + this.product.price.toString()),
StarRating(), // Using the previously defined Rating widget
],
),
),
)
],
),
),
);
}
}