Ephemeral and Application State in Flutter
State is data that an application needs to display appropriate content and function correctly. It is divided into two conceptual types: Ephemeral State and Application State. This article helps to distinguish between them.
Table of Contents
Ephemeral State
Ephemeral state, also known as UI state, refers to temporary data in a system that is lost or reset when a component is re-rendered or unmounted. Ephemeral state is local and short-lived, and doesn't require state management tools like Bloc.
Examples of ephemeral state include:
- input value of
TextField - current index of
NavigationBarorPageView - progress of animation
- locally filtered array
Best Practices
Check out these three rules:
- DO keep ephemeral state locally. Store UI-specific data (e.g., input values, selected indices) within the widget that needs it (using a
StatefulWidget). - DON'T overcomplicate – keep ephemeral state simple. For example, do not create a bloc that stores current index of a bottom navigation bar.
- DON'T rely on ephemeral state for business logic. For example, do not perform complex logic (e.g. network requests) in widgets.
Application State
Application State represents data that is essential to the functioning of the application. It has a broader scope and may persist over longer periods of time. This state is often shared across multiple components and requires a State Management Solution.
Examples of application state include:
- A list of posts fetched from the server in Twitter
- Logged in user details and profile
- Application-wide settings such as theme or language preferences
Application state is stored in a state management tool. Units of the state management tool, such as controllers, should be managed by stateful widgets that can create and dispose them gracefully.
For more information about Application State, see Designing State Management and Business Logic.
Architectural Insights
The ability to distinguish between state types can provide important architectural insights.
Consider the following example. You have a Home screen with tab bar and two tabs: Posts and Users. The Users tab has a "Show Posts" button that navigates to the Posts tab and applies a filter. Because the active index of the tab bar is stored in Home screen, you can't change it from the Users tab – the state of the TabBar is ephemeral.
The first thing that comes to mind is the idea that the TabBar state now becomes the application state and requires a state management solution to store active index. Now, that button can update the index easily:
// somewhere in Users Tab
Button(
label: 'Show Posts',
onPressed: () {
context.read<TabIndexController>().updateIndex(0);
context.read<PostsController>().loadPosts(filter: userId);
},
);While this works, it has drawbacks. First of all, the controller, which shouldn't know anything about its clients, and should be platform, device and environment independent, knows about a specific function of a specific widget of a specific framework.
The entire Users tab has hidden dependencies on TabIndexController and PostsController. When testing the Users tab view and specific widgets used within it, you will find that you need to provide these two controllers.
Should the Users tab control the index of a tab bar? Should it know about the specifics of the Posts tab? Should it know about the Posts tab at all? No, the system should be decoupled and components of it should follow Single Responsibility Principle (SRP). It's the interesting part of architecture. One broken brick can bring down an entire wall.
To fix it, you can create a callback for the button. The parent of a button defines this callback, the parent of a parent until you reach the Home screen:
// In Home screen
void _onShowPostsPressed(String userId) {
setState(() => _selectedIndex = 0);
postsController.loadPosts(filter: userId);
}Now the decision of what to do when the user clicks the button is up to the home screen, which knows about both tabs. This approach keeps the system decoupled. Testing is more predictable, and the Users tab is self-contained.
However, you might ask, doesn't applying this kind of logic to the home screen violate the SRP? The responsibility of the home screen is to meet the needs of a user who wants to access the pages of the tab bar. This includes rendering the tab bar, managing the current index, orchestrating the logic (as we did with showPosts), and so on.
Conclusion
There are two types of state - ephemeral and application. Ephemeral is usually short-lived and is stored in the stateful widget. To update ephemeral state, you can use setState. Application state is a more complex state that is usually shared between different components. It contains more complex data that can be persisted. Application state requires a state management solution such as bloc.