Getting Started

Welcome to the documentation of Paper for Flutter app development. This is a solution for Flutter app state management. We hope this guide will help you to comprehend and effectively implement the pattern.

Discussion

Currently, there are plenty of state management approaches for Flutter apps. But most of them fall into one kind of architecture:

  • Observables (InheritedWidget, Provider, BLOCs, etc.) stay somewhere in the UI tree.
  • UIs (widgets) below the Observables get injected with Observables and observe them.
  • Whenever the Observables change, the UIs will be notified and react accordingly.

The pros and cons of each approach have been listed in many articles. They will not be discussed here. Instead, we will focus on the design architecture as mentioned above.

After working with several projects, from small to large, using different state management approaches (InheritedWidget, Provider, BLOCs, etc.), we found that they all fall into some common drawbacks:

  • Because the UIs depend on the Observables, the UI components are hard to reuse. The bigger the components are, the harder they are to reuse. For example, those components might need to be reused in many places in the app but do not need to change state through a common Observable.

  • When UI components subscribe to many Observables, the logic of processing reactions to those Observables makes the components too big compared to the scope of the UIs.

  • The business logic code is claimed to be separated from UI and easy to maintain. But eventually, that logic code piles up and gets clogged somewhere when the project expands.

  • This kind of architecture also creates an un-unified communication structure between objects. For example, we have an ElevatedButton with inkwell animation whenever it is tapped and triggers a callback. If following the above-mentioned patterns, there should be an Observable that reflects the tap state. When the button is tapped, an action will trigger the Observable to change state and the button will execute the animation accordingly and trigger the callback. There are also other objects like TextField, Slider, with the same situation. On the other hand, they change state without any Observables from above them or from a dependency injection container.

Idea

After experiencing the mentioned drawbacks, we came up with a "different" approach, which is actually not something new or innovative. In contrast, it is very conventional.

Instead of subscribing to any "above" Observables, all components that build up the app are Observables themselves.

Think of the app as structured like a tree, very much like a context tree, where each context is observable and gets subscribed to by its directly above context (the parent). The context will handle the state change by itself as it is designed and notify its parent via a listener (callback, stream events, etc.). The parent will give a "command" to children to change state whenever needed. Take ElevatedButton or any kind of button as an example: when the button is tapped, it will change its state (conduct the inkwell animation or flashing color) and notify the parent to continue to do something accordingly.

Yes, this approach will have an annoying issue that all current state management approaches have tried to avoid and solve: the state will be passed down by each component. But after testing, we believe that the passing down is not really as messy as claimed and it is worth the trade-off for all the trouble encountered in those state management approaches.