State Management

State Management

Master the Art of Flutter

What is state management?

State management is the process of managing the state of a Flutter app. The state of an app is the data that changes over time, such as the text in a text field, the selected item in a list, or the current position on a map.

Why is state management important?

State management is important because it allows you to create dynamic and interactive user interfaces. For example, if you want to display a list of items that the user can select, you need to keep track of which item is currently selected. This is done by managing the state of the list.

Different approaches to state management in Flutter

There are many different approaches to state management in Flutter. Some of the most common approaches include:

  • Implicit state management: This is the simplest approach to state management, but it can be difficult to use for complex apps. With implicit state management, the state of a widget is stored in the widget itself.

    Key points about implicit state management:

    • It's suitable for small-scale, localized state management within a single widget or its subtree.

    • It's straightforward to implement and understand.

    • It can become cumbersome to manage complex state that needs to be shared across multiple widgets.

  • Explicit state management: This approach is more complex than implicit state management, but it gives you more control over the state of your app. With explicit state management, the state of the app is stored in a separate object, such as a StatefulWidget or a ChangeNotifier.

    Key points about explicit state management with Provider:

    • Separates state from UI for better organization and testability.

    • Facilitates sharing state across multiple widgets without complex widget hierarchies.

    • Provides a clear structure for managing state updates and UI rebuilds.

    • Offers a relatively simple approach compared to some other state management solutions.

  • State management libraries: There are many third-party state management libraries available for Flutter. Some of the most popular libraries include Provider, Riverpod, and BLoC.

Which state management approach should you use?

The best state management approach for your app will depend on its complexity and your own preferences. If you are building a simple app, you may be able to use implicit state management. However, if you are building a complex app, you should use explicit state management or a state management library.

Example of state management in Flutter

Here is an example of how to use state management in Flutter to create a simple todo app.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// to_do_model.dart
final toDoListProvider = StateNotifierProvider<ToDoList, List<ToDo>>((ref) {
  return ToDoList([]);
});

class ToDoList extends StateNotifier<List<ToDo>> {
  ToDoList(List<ToDo> initialTodos) : super(initialTodos);

  void addTodo(String title) {
    state = [...state, ToDo(title)];
  }

  void toggleTodo(ToDo todo) {
    state = [
      for (final todo in state)
        if (todo.id == todo.id) todo.copyWith(isCompleted: !todo.isCompleted) else todo
    ];
  }
}

class ToDo {
  final String title;
  final bool isCompleted;
  final int id;

  ToDo(this.title, {this.isCompleted = false, this.id = UniqueKey().hashCode});

  ToDo copyWith({bool? isCompleted}) {
    return ToDo(title, isCompleted: isCompleted ?? this.isCompleted, id: id);
  }
}

// main.dart
void main() {
  runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ToDoListScreen(),
    );
  }
}

// to_do_list_screen.dart
class ToDoListScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final toDoList = ref.watch(toDoListProvider);

    return Scaffold(
      appBar: AppBar(title: Text('To-Do List')),
      body: ListView.builder(
        itemCount: toDoList.length,
        itemBuilder: (context, index) {
          final todo = toDoList[index];
          return ListTile(
            title: Text(todo.title),
            leading: Checkbox(
              value: todo.isCompleted,
              onChanged: (value) => ref.read(toDoListProvider.notifier).toggleTodo(todo),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => showDialog(
          context: context,
          builder: (context) => AddToDoDialog(ref),
        ),
        child: Icon(Icons.add),
      ),
    );
  }
}

// add_to_do_dialog.dart
class AddToDoDialog extends ConsumerWidget {
  final _titleController = TextEditingController();

  AddToDoDialog(this.ref);

  final WidgetRef ref;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return AlertDialog(
      title: Text('Add To-Do'),
      content: TextField(
        controller: _titleController,
        decoration: InputDecoration(hintText: 'Enter todo title'),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: Text('Cancel'),
        ),
        TextButton(
          onPressed: () {
            ref.read(toDoListProvider.notifier).addTodo(_titleController.text);
            Navigator.pop(context);
          },
          child: Text('Add'),
        ),
      ],
    );
  }
}

Follow these steps to build a todo app.

1. Set up Riverpod:

  • Import the flutter_riverpod package.

  • Wrap the entire app with ProviderScope to make providers accessible throughout the widget tree.

2. Create a StateNotifierProvider for state management:

  • Define a toDoListProvider using StateNotifierProvider.

  • This provider holds a ToDoList class, which is a StateNotifier that manages the to-do list data.

3. Implement the ToDoList class:

  • Extend ToDoList from StateNotifier<List<ToDo>> to manage the state of a list of ToDo items.

  • Define methods within ToDoList to add and toggle todo items within the list.

4. Access and use the provider in widgets:

  • Use ConsumerWidget to access providers and rebuild when state changes.

  • Use ref.watch(toDoListProvider) to access the current to-do list.

  • Use ref.read(toDoListProvider.notifier) to modify the state by calling methods on the ToDoList instance.

5. Handle user interactions:

  • Use UI elements like buttons and checkboxes to trigger state changes.

  • When a user interaction occurs, call the appropriate methods on the ToDoList notifier to update the state.

6. Rebuild UI based on state changes:

  • Riverpod automatically rebuilds widgets that depend on the changed state, ensuring the UI always reflects the latest data.

Conclusion

State management is an important part of Flutter development. By understanding state management, you can create dynamic and interactive user interfaces.

There are many different approaches to state management in Flutter. The best approach for your app will depend on its complexity and your own preferences.

In the next article, we will see how to navigate between screens in Flutter apps including routing arguments, named routes, navigation bar, and tabs.