You Don't Need That State Management Library (Yes, Even in Flutter)

You Don't Need That State Management Library (Yes, Even in Flutter)

Every Flutter tutorial tells you the same thing: pick a state management library on day one. Riverpod. Bloc. GetX if you're feeling adventurous. And honestly? For most apps, that's overkill.

We've shipped Flutter apps that handle real money, real users, real complexity — and some of them don't use a single state management package. Not because we're purists. Because we didn't need one.

This isn't a takedown of Riverpod or Bloc. They're excellent tools. But there's a difference between reaching for a tool because you need it and reaching for it because a Medium article told you to.


The Cargo Cult Problem

Here's what we see constantly: developers add Riverpod to a project with three screens and a login flow. They create providers for everything. They write boilerplate to manage state that could live in a StatefulWidget. Then they spend hours debugging provider scope issues.

Why? Because "that's how you do Flutter."

Except it's not. Flutter ships with state management built in. The framework's authors didn't forget to include it — they just didn't market it as aggressively as library maintainers do.

The dirty secret is that setState, InheritedWidget, and ValueNotifier can take you surprisingly far. We're talking apps with 20+ screens, authentication, real-time data, the works. These tools aren't training wheels. They're the actual engine.

Comparison of over-engineered state management versus simple built-in Flutter tools

What Flutter Gives You Out of the Box

Let's be specific about what "built-in" actually means.

setState — Yes, the one everyone dismisses. It's synchronous, predictable, and rebuilds exactly what you tell it to. For local widget state, it's perfect. A form? setState. A toggle? setState. An animation controller? setState. Stop overthinking it.

InheritedWidget — This is Flutter's dependency injection and state sharing mechanism. Provider is literally a wrapper around it. When you need to pass data down the tree without prop drilling, InheritedWidget does exactly that. It's more verbose than Provider, sure. But it's zero dependencies and you understand exactly what's happening.

ValueNotifier + ValueListenableBuilder — Reactive state without a library. Create a ValueNotifier, listen to it anywhere, rebuild only the widgets that care. We use this pattern constantly for things like theme toggles, feature flags, and simple shared state.

ChangeNotifier + ListenableBuilder — Same idea, but for objects with multiple properties. Your "view model" can just be a ChangeNotifier. Call notifyListeners() when something changes. Done.

At Etere Studio, we've built entire features using just these four primitives. Not because we're stubborn — because they were enough.

Diagram of Flutter built-in state management tools stacked as building blocks

When You Actually Need a Library

Okay, so when should you reach for Riverpod or Bloc? Real situations, not theoretical ones:

Complex dependency graphs. If your app has services that depend on other services that depend on async initialization that depends on user state — yeah, a DI solution helps. Riverpod's provider dependencies are genuinely elegant here.

Team scale. Five developers touching the same codebase? Enforced patterns matter. Bloc's strict separation of events, states, and logic creates consistency that code review alone can't achieve.

Testability requirements. If you need to mock everything for extensive unit testing, libraries like Riverpod make overriding dependencies trivial. Built-in tools can do it, but with more ceremony.

Caching and deduplication. Riverpod's automatic caching and request deduplication is legitimately hard to replicate with vanilla Flutter. If you're making lots of API calls that might overlap, this matters.

Notice what's not on the list: "because my app has state." Every app has state. That's not a reason.


A Practical Heuristic

Here's the rule we use: start with built-in tools. Add a library when you feel pain, not when you anticipate it.

Building an MVP? setState and maybe a ChangeNotifier or two. Ship it. Learn where the actual complexity lives.

Adding features and feeling friction? Now you have real information. Maybe you need better dependency injection. Maybe you need stricter architecture. Maybe you just need to refactor that one God widget.

The point is: you'll know. And you'll add exactly the abstraction you need, not the one some conference talk recommended for apps ten times your size.

We've seen teams spend weeks setting up elaborate state management architectures for apps that never launched. Meanwhile, scrappy MVPs with "messy" setState code are making money and iterating fast.


The Uncomfortable Truth

Good engineers master their framework's primitives before adding abstractions. That's not a Flutter opinion — it's a general principle.

If you can't explain how InheritedWidget works, you probably shouldn't be using Provider. If you don't understand Flutter's widget lifecycle, Bloc won't save you from bugs — it'll just make them harder to trace.

Learn the foundation. Build something real with it. Then decide if you need more.

Most of the time, you won't.


Building a Flutter app and not sure what you actually need? We help teams cut through the noise. Let's talk.