flutter-estado-prop-drilling

Prop Drilling and Shared State in Flutter

  • 4 min

The application state is the information that describes how the app is.

Imagine you are building an e-commerce application. You have a “Shopping Cart”.

That cart (a list of products) needs to be visible in:

  1. The Catalog screen (to show a counter “3 items”).
  2. The Product Detail screen (to add more).
  3. The Checkout screen (to pay).

setState only rebuilds the State it belongs to. We can lift _cart to a common ancestor and pass it down, but that solution becomes cumbersome when there are many levels.

Local State and Shared State

To solve this, we must first distinguish two types of data:

It is a state that only matters to a specific widget.

  • Examples: The selected tab in a menu, whether an animation is running, the text you are typing in an input.
  • Solution: Here setState is usually sufficient.

It is a state that needs to be shared by several widgets in different parts of the App.

  • Examples: Whether the user is logged in, the cart contents, the theme (dark/light), unread notifications.
  • Solution: We can lift the state up or provide an object to the part of the tree that needs it.

The Problem of Prop Drilling

The native “brute force” solution for sharing data is called Lifting State Up.

It involves moving the cart variable to the “Grandparent” widget common to all screens (for example, to Main). And from there, passing the data down through the constructors.

This generates the phenomenon known as Prop Drilling or “The Constructor Hell”.

The Nightmare Scenario

Imagine that the Grandparent widget has the User data. And the Grandchild widget needs to display the user’s name.

For the data to arrive, it must pass through the Parent. But the Parent does not care about the user, it does not use it for anything. It just acts as a pipeline.

// 1. THE GRANDPARENT (Has the data)
class Grandparent extends StatelessWidget {
  final User usuario = User(name: "Luis");

  @override
  Widget build(BuildContext context) {
    // Has to pass it to the parent...
    return Parent(usuario: usuario);
  }
}

// 2. THE PARENT (The unnecessary pipeline)
class Parent extends StatelessWidget {
  final User usuario; // Receives the data EVEN THOUGH IT DOES NOT USE IT
  
  const Parent({required this.usuario});

  @override
  Widget build(BuildContext context) {
    return Container(
      // ...and passes it to the grandchild
      child: Grandchild(usuario: usuario),
    );
  }
}

// 3. THE GRANDCHILD (Who actually needs it)
class Grandchild extends StatelessWidget {
  final User usuario;
  
  const Grandchild({required this.usuario});

  @override
  Widget build(BuildContext context) {
    return Text("Hello, ${usuario.name}");
  }
}
Copied!

Why It Can Be a Problem

  1. Messy Code: If you have a hierarchy of 10 widgets, you have to modify 10 files to pass a new variable.
  2. Coupling: The Parent widget now depends on User. If you change the User object, you have to touch the Parent, even though it doesn’t even render it.
  3. Rebuilds: If the data changes up the tree, some intermediate widgets may re-execute build even though they don’t use that value.

Providing Data to a Subtree

What we really want is a way for the Grandparent to have the data, and for the Grandchild to access it directly without bothering the Parent.

We want to provide a dependency to the descendants that need it.

We want to be able to say anywhere in the App:

“Hey, is there any Cart available up there? If so, give it to me.”

The Solution: Provider

To solve this, we use the State Management ecosystem. There are many libraries (Riverpod, Bloc, GetX, MobX), but Provider remains a very good option to start with because the official Flutter documentation uses it as a simple approach and teaches concepts that later appear in other solutions.

Provider is, in essence, a wrapper over a native Flutter tool called InheritedWidget, but much easier to use.

Provider allows us to:

  1. Place data at the top of the widget tree.
  2. Make any descendant widget listen to that data.
  3. If the data changes, only the widgets that have subscribed to it are rebuilt, not the entire application.