It’s been almost a year of my professional Flutter career and I have experienced many packages and utilities. This post is about the ones I use frequently and the reasons why I chose them.

State management

Good topic to bring at family gatherings. Well, apart from politics and religious beliefs. Each one has his own opinion—this one is mine and you should take it with a grain of salt.

flutter_bloc

BLoC is the only state management solution I tried that scaled well for larger apps.

BLoC is defined with states and events. You are forced to precisely define all state possibilities before anything else. I prefer it this way because when you get to writing logic which handles these states it is fairly straightforward. Also, it is hard to get some weird undefined state (like when the user logs out and you still display his username).

provider

I started with Provider which is recommended as the “state management”1 solution on official Flutter docs. It does one thing well and fast: passing a widget to its descendants. I would use it in simple projects but in my experience it does not scale well.

Once you have multiple providers and want them to communicate (parent provider notifies child provider to change state) things get less optimal. You can use proxy providers but it brings a lot of unnecessary boilerplate.

Immutability

Immutable classes/states works nicely with BLoC. Rather than changing (mutating) old state you create new one.

freezed

Freezed generates immutable classes for you. You define an abstract class with factory constructors. Freezed then uses code generation to create respective implementations. Following is a snippet from its docs.

@freezed
abstract class Union with _$Union {
  const factory Union.loading() = Loading;
  const factory Union.error(String message) = ErrorDetails;
}

Together with instantiable classes, you get various methods which make life easier, like when or map.

Union state = Union.loading();

state.when(
    loading: () => CircularProgressIndicator(),
    error: (message) => Text(message),
);

Dependency injection

flutter_bloc

For simple scoped dependency injections I use RepositoryProvider from BLoC.2 The only downside to this is missing extension methods auto-complete support in Android Studio. You need to manually import BLoC to see the repository method on context.

var repository = context.repository<UserRepository>();
print(repository.name);

I personally don’t like that it’s called repository provider. But it is just a nitpick.

injectable

In some more complicated cases I prefer to use injectable which is a code generator for get_it. You add annotations like @singleton to your classes and injectable automatically registers them.

What I find useful is the support for registering third party modules (e.g. shared_preferences instance) and async registration.

Routing

auto_route

Automatically generates named routes for you. I like that you define all routes at a single place (you can have multiple routers). Also, you can get navigators by their name which comes handy with features like the bottom navigation bar.

Persistence

floor

My personal favorite. Well, I would like it to be my favorite. It tries to work exactly like the Room library on Android. You define entities (classes) and annotate them. floor will then generate the required SQL code for you. Streamed queries are a joy to use and together with StreamBuilder makes your life better.

However, floor misses few advanced features like type adapters (WIP apparently), database encryption or SQLite extensions. You might manually setup the latter 2 things but it is not as easier as in moor.

I haven’t tried moor yet, because I never liked the API. However, it seems to be have more features and development (due to larger community).

hive

First persistence library I used. Its simplicity is a superb feature but lacks development at this very moment. Still useful for quick NoSQL persistence (offline cache of JSON objects or similar).

Other

rxdart

Once you go the Stream way you never go back. The rxdart comes handy when you already use streamed data (queries in floor or bloc). You will not need it most of the time because Dart’s standard Stream library has already enough features. But once you need to add more complex things like debouncing, it can save you a lot of time.


  1. Provider is rather a dependency injector. ChangeNotifier class it uses for the state is part of the standard Flutter library. ↩︎

  2. RepositoryProvider in BLoC uses Provider under the hood. ↩︎