Flutter: State management for entities
What is an Entity?
An Entity is a concept or thing that has a unique identity and represents something meaningful in the problem domain or system being modeled or built.
According to DDD (Domain-Driven Design): An entity is an object that has an identity that spans time and different states. It is fundamental in DDD to distinguish between entities and value objects. Entities have a unique identity (such as an ID or serial number), while value objects are identified by their attributes
Why to use state management in Flutter Apps?
State management in Flutter apps is essential for efficiently handling the dynamic nature of user interfaces. As apps grow in complexity, tracking and updating the state of various components can become challenging. State management solutions provide a structured way to manage this complexity, ensuring that the app remains responsive and the user experience remains consistent. Moreover, using state management tools can lead to more maintainable code, as they often encourage separation of concerns, making it easier for developers to understand, debug, and extend the application over time.
A framework for State Driven Development (SDD):
The Problem with Traditional UI Development is that mistakenly the state of an application is considered a mere side effect of the actions within the application. State Driven Development (SDD) emphasizes the importance of an application’s state in the development process. We need a framework (or library) to handle this problem with a State-First Approach
The Spread package offers a simplified way of managing the state within Flutter applications. With the use of entities, subscribers, and state emitters, developers can easily store, observe, and manipulate states without the usual boilerplate.
Benefits of SDD:
- Reduced Cognitive Load: By focusing on state, developers can think about individual application states in isolation, ensuring that changes in one state don’t unexpectedly affect another.
- Ease of Testing: With well-defined paths through the application, snapshot testing becomes straightforward. The clear distinction of states simplifies the testing process.
- Ease of Visual Documentation: SDD’s compartmentalized approach makes it easier to visually represent the application’s flow and behavior.
- Close Ties with Behaviour Driven Development (BDD): SDD aligns with BDD techniques, emphasizing the behaviors of the application mapped out through state and actions.
Implementing State management for entities:
- Create your Entity
import 'package:spread/spread.dart';
class User implements Entity {
final int id;
final String name;
final List<UserPost> posts = List.empty(growable: true);
// constructor
User({required this.id, required this.name});
// an Entity must provide an entityId getter.
@override
String get entityId => id.toString();
}
spread package provides a contract (interface) for Entities: all Entities must provide an entityId getter.
2. Create a Widget to show your entity:
import 'package:flutter/material.dart';
import 'package:spread/spread.dart';
import 'user.dart';
class UserItem extends StatelessWidget {
final User user;
const UserItem({super.key, required this.user});
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(user.id.toString()),
subtitle: Row(
children: [
Text(user.name.toString()),
const SizedBox(width: 10),
Spread<User>(
entity: user,
stateCondition: (User? entity) => entity!.posts.length.isEven,
builder: (BuildContext context, User? entity) {
return Text('- posts: ${entity!.posts.length.toString()}');
})
],
));
}
}
In this example, the UserItem Widget shows properties of the user like id, name, and posts counter. The posts counter is the unique property updated in real time. We use the Spread Widget to handle the UI updates when a User with a specific ID (entity) is emitted.
Notice that the Spread Widget is parameterized with the “User” class and the “entity” argument of the constructor is set with the user object (the Entity). The Spread widget can get the entity ID and subscribe to changes to a User object with that entity ID (Because the User class implements the Entity interface).
When a User object with the same entity ID is emitted, Spread Widget evaluates the stateCondition function and if it is true, invokes de builder function and updates the UI.
3. Emit your Entity:
import 'package:spread/spread.dart';
import 'user.dart';
class RefreshUserPosts with StateEmitter implements UseCase {
final User user;
RefreshUserPosts(this.user);
@override
void execute() async {
emitEntity<User>(user);
}
}
In this example we have a UseCase combined with the StateEmitter Mixin. This Mixin provides a set of functions to emit states:
- emitNamed(name, state) for named states
- emit<T>(state) for typed states
- emitEntity<E>(entity) for entity states.
The execute method is provided by the UseCase interface. Once the state is emitted, all subscribers for that state will receive the emitted state.
Conclusion:
State management in Flutter applications is an essential tool for handling the ever-changing dynamics of user interfaces. As applications become more complex, it’s imperative to have a robust system in place to efficiently track and update component states. The State Driven Development (SDD) approach and the Spread package offer streamlined solutions to this challenge, emphasizing the importance of state in the development process. By adopting these practices and tools, developers can not only enhance the reactivity and consistency of their applications but also reduce cognitive load, facilitate testing, and improve code maintainability. In summary, to build scalable and high-performing Flutter applications, it’s crucial to adopt an effective and well-structured state management strategy.