Consideration of Bucket Relay in Flutter’s Widget Tree

Ryoichi Izumita
5 min readFeb 20, 2024

--

Photo by Tony Mucci on Unsplash

One of the problems often encountered in designing Widgets for Flutter apps is the “bucket relay” of state management. This article examines ways to solve the bucket relay problem.

What is a Bucket Relay?

Bucket relay refers to the act of a series of widgets passing parameters from parent to child.

In Flutter, this bucket relay appears in two main forms:

  1. when a parent Widget passes state directly to a child Widget: in this case, state is “handed off” from parent to child. This is a useful and simple way to pass state, but can be problematic if the widget hierarchy is deep or multiple widgets need the state.
  2. When a parent Widget passes a callback to a child Widget, and the child Widget communicates its state to its parent by calling the callback: The child Widget can communicate its state to the parent Widget. However, this can become complicated if the widget hierarchy is deep or if multiple widgets need to manage the same state.

These bucket relays can increase complexity in medium to large Flutter applications. Several state management solutions (e.g. Provider, Riverpod, Redux, etc.) are available to solve such problems. However, rethinking the Widget design before relying on such libraries may alleviate this problem.

Bucket relays to avoid and bucket relays that do not need to be avoided

Bucket relays should generally be avoided because they usually reduce code readability and maintainability. However, not all bucket relays are necessarily bad.

Bucket relays to avoid

  • Bucket relays in unnecessarily deep widget trees: When state is passed across multiple levels in a deeply nested widget tree, the state becomes difficult to easily track and manage. This makes code difficult to understand and can create bugs. Such bucket relays should be avoided.

Bucket relays that need not be avoided

  • Bucket relays in shallow widget trees: Bucket relays to the extent of passing state to a direct child widget do not necessarily need to be avoided. In this case, removing and adding state can remain intuitive and code complexity can remain low.
  • Consistent bucket relays: If some state flows consistently through a particular Widget subtree, its bucket relays need not be avoided. This is predictable as a consistent pattern and maintains code readability and maintainability.

To avoid bucket relays that should be avoided, focus on the following.

Single Responsibility Principle and State Management

The Single Responsibility Principle is one of the design principles for components and modules. This principle states that a module “should be responsible for only one role. Applying this to Flutter’s Widget design, the guiding principle is that each Widget should only maintain the state it needs and should have no other information.

A design that violates the single-responsibility principle requires one Widget to have the state of another Widget. This creates a bucket relay as data passes through the tree of widgets. In other words, state is handed off one by one from higher-level components to lower-level components, each of which must have state, which can be difficult to manage.
Therefore, adhering to the single responsibility principle makes each component or function have one responsibility, thus clarifying their roles and improving manageability. It also prevents bucket relays from occurring, since the state can be passed directly to the component that needs a particular state. The result is a more direct and predictable flow of data and simplified state management.

Referring to the Atomic design, a simple Widget tree structure might look like Page→Organisms→Molecules→Atoms. In this case, Organisms holds the external state and the value is passed to Molecules and its child Atoms. In this case, there is no problem in passing values directly, but as the state becomes more complex, bucket relays are more likely to occur. In particular, when a Molecule has Molecules as children, the state flow is complicated from Organisms to Molecules to Molecules, and there is a high possibility that the intermediate Molecules will violate the principle of single responsibility. In this case, for example, Organisms could be designed to create grandchild Molecules and pass them to child Molecules.

Internal and external state

State management in programming controls the behavior or behavior of an application. This is usually divided into two categories: internal state and external state.

Internal state is a state that is closed within a particular component or class. Internal state is usually used to determine how the component should behave or what it should display at a given point in time. It is also tied to the life cycle of the component, being initialized when the component is mounted and erased when the component is unmounted. Internal state is typically used to control a specific function or behavior of a component. This state is managed by the component itself and usually only that component can change its state.

External state, on the other hand, typically controls the behavior of the entire application across multiple components or parts. External state is usually managed via a state management mechanism (such as Provider or Riverpod) to have overall application state. External state is ideal for managing data that affects the entire application (e.g., user information, application settings, shared resources, etc.) so that all necessary components can access and modify that state. External state is centrally managed in a state management library, whose store handles any state changes that may occur in any part of the application.

By properly separating internal and external state, this bucket relay can be mitigated.

  • Internal state: Internal state is generally referenced and manipulated by the widget itself and does not affect other widgets. Therefore, this state is internal to the widget and does not need to be handed off to other widgets. This prevents unnecessary passing of state (bucket relay) in the widget tree.
  • External state: External state is information that is shared throughout the application and may be referenced and manipulated by multiple widgets. This information is managed in a centralized store (state management library) and necessary widgets retrieve state directly from the store. Widgets do not have to pass state sequentially from parent to child, thus reducing the bucket relay.

Summary

Bucket relay is the process by which a widget passes state to its child widgets, which, if repeated, complicates the flow of data between widgets and reduces readability and maintainability. One way to solve this problem is to use a state management library, but before doing so, it is important to consider the design of a more basic solution.

Proper design of the widget itself and the widget tree can greatly reduce the bucket relay problem. Specifically, based on the single responsibility principle, each widget should be designed to have only one responsibility and to separate internal and external state.

Then, as the application grows in size and requires more complex state management, you can consider eliminating bucket relay with a state management library.

In conclusion, designing widgets and their trees is the first critical step, followed by effective use of state management libraries for smoother and more efficient Flutter app development. The right combination of these techniques can solve the bucket relay problem and improve code readability, maintainability, and efficiency.

--

--

Ryoichi Izumita
Ryoichi Izumita

Written by Ryoichi Izumita

iOS / Flutter / Objective-C / Swift / Dart

No responses yet