State
The application state is all the data that your app needs to function, and that can change over time.
Usually you'd have a class that represents your app state.
Since State
is a name already taken in Flutter, you can call it AppState
.
This is an example:
class AppState {
final String name;
final int age;
AppState({required this.name, required this.age});
}
It's optional but common to also define a static method initialState
that returns an instance
of the state with initial values:
class AppState {
final String name;
final int age;
AppState({required this.name, required this.age});
static AppState initialState() => AppState(name: "", age: 0);
}
The immutability requirement
In Redux, the state class must be immutable. This means that you can't change it directly.
Note: If you're not familiar with immutability, it means that once an object is created, you can't change its fields. Usually the fields are marked as
final
.
Instead, you need to create a new AppState
object every time you need to change the app state.
This is simple to do. For example, methods withName
and withAge
below
return a new AppState
object with the new name or age:
class AppState {
final String name;
final int age;
AppState({required this.name, required this.age});
static AppState initialState() => AppState(name: "", age: 0);
AppState withName(String name) => AppState(name: name, age: age);
AppState withAge(int age) => AppState(name: name, age: age);
}
A common pattern is having a copy
or copyWith
method that allows you to change multiple fields
at once:
class AppState {
...
AppState withName(String name) => copy(name: name);
AppState withAge(int age) => copy(age: age);
AppState copy({String? name, int? age}) =>
AppState(
name: name ?? this.name,
age: age ?? this.age
);
}
Your state can be composed of any immutable objects,
including other immutable objects that you create.
For example, the following is a state that represents a Todo List.
The Todo
class is immutable:
class AppState {
final IList<Todo> todos;
AppState({required this.todos});
static AppState initialState() => AppState(todos: []);
}
class Todo {
final String description;
final bool done;
Todo({required this.description, this.done = false});
}
Note that the todos
field is an immutable list of type IList
, provided by the
fast_immutable_collections
package, which was also created by me.
You don't need to use package fast_immutable_collections
, but it's recommended because it
provides immutable lists, sets and maps that are easier to use than trying to use standard Dart collections
like List
in an immutable way.
To add and remove items from the list, you can use the IList.add
and IList.remove
methods:
class AppState {
final IList<Todo> todos;
AppState({required this.todos});
static AppState initialState() => AppState(todos: IList<Todo>());
AppState copy({IList<Todo>? todos}) =>
AppState(todos: todos ?? this.todos);
AppState add(Todo todo) => copy(todos: todos.add(todo));
AppState remove(Todo todo) => copy(todos: todos.remove(todo);
}
Single state
If you think having a single state class to represent all your app state is too restrictive, it's actually not. You can have multiple state classes, as long as you put them all inside a single class in the end. For example, suppose we need to represent a state that has a Todo List plus some user information:
// Todo List
class TodoList {
final IList<Todo> list;
TodoList({this.list = const IList<Todo>()});
TodoList copy({IList<Todo>? list}) =>
TodoList(list: list ?? this.list);
TodoList add(Todo todo) => copy(list: list.add(todo));
TodoList remove(Todo todo) => copy(list: list.remove(todo));
}
// User information
class User {
final String name;
final int age;
User({this.name = "", this.age = 0});
User copy({String? name, int? age}) =>
User(name: name ?? this.name, age: age ?? this.age);
}
You can then create a single AppState
class that holds both the TodoList
and the User
:
class AppState {
final TodoList todoList;
final User user;
AppState({required this.todoList, required this.user});
static AppState initialState() =>
AppState(todoList: TodoList(), user: User());
AppState copy({TodoList? todoList, User? user}) =>
AppState(todoList: todoList ?? this.todoList, user: user ?? this.user);
AppState withTodoList(LodoList todoList) => copy(todoList: todoList);
AppState withUser(User user) => copy(user: user);
}
Now that we know how to create the state, let's see next how to create the Async Redux store that will hold it.