Skip to content
← writing

February 20, 2026 · 3 min read

The Flutter Packages I Reach For

More packages isn't better. The pub.dev dependencies that consistently earn their place in my Flutter projects — bloc, dio, freezed, go_router, get_it and friends — and how I decide.

The Dart ecosystem on pub.dev is one of Flutter's quiet superpowers. But more packages isn't better — every dependency is code you don't control and have to maintain around. These are the ones that consistently earn their place in my projects, and why.

State management: bloc

flutter_bloc makes state explicit and testable. Events go in, states come out, and your UI is a pure function of the current state.

class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0);
  void increment() => emit(state + 1);
}

// In the widget tree:
BlocBuilder<CounterCubit, int>(
  builder: (context, count) => Text('$count'),
);

If you prefer a lighter, provider-style API, Riverpod is excellent and compile-safe. Both are good — pick one and be consistent.

Networking: dio

dio is http with the things you'll eventually need built in: interceptors, timeouts, cancellation, and form data.

final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'))
  ..interceptors.add(InterceptorsWrapper(
    onRequest: (options, handler) {
      options.headers['Authorization'] = 'Bearer $token';
      handler.next(options);
    },
  ));

final res = await dio.get('/articles');

Pair it with retrofit to generate a typed API client from an annotated interface.

Immutable models: freezed + json_serializable

Writing copyWith, ==, hashCode, and fromJson by hand is error-prone busywork. freezed generates all of it, plus sealed unions for your states.

@freezed
class Article with _$Article {
  const factory Article({
    required String id,
    required String title,
    @Default(false) bool published,
  }) = _Article;

  factory Article.fromJson(Map<String, dynamic> json) => _$ArticleFromJson(json);
}

Run dart run build_runner build and you get value equality, copyWith, and JSON — for free. build_runner is the code-gen engine the whole ecosystem shares.

Navigation: go_router

Declarative, URL-based routing that works with deep links and the web out of the box.

final router = GoRouter(
  routes: [
    GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
    GoRoute(
      path: '/article/:id',
      builder: (_, state) => ArticleScreen(id: state.pathParameters['id']!),
    ),
  ],
);

auto_route is a strong alternative if you'd rather generate type-safe routes from annotations.

Dependency injection: get_it (+ injectable)

A service locator to wire your layers together without passing objects through five constructors. Add injectable to register dependencies with annotations instead of by hand.

final sl = GetIt.instance;
sl.registerLazySingleton<ArticleRepository>(() => ArticleRepositoryImpl(sl()));
final repo = sl<ArticleRepository>();

The unglamorous essentials

  • logger — readable, leveled logs instead of scattered print calls.
  • equatable — value equality without code-gen when freezed is overkill.
  • shared_preferences / hive / isar — key-value and local database options, from simple to full offline persistence.
  • cached_network_image — image loading with caching and placeholders, solved.

Ops and quality

Beyond app code, a few tools make delivery serious:

  • FVM pins the Flutter SDK version per project, so "works on my machine" stops being a phrase.
  • Flutter flavors separate dev/staging/prod builds with their own configs and app IDs.
  • freeRASP adds runtime app self-protection (root/jailbreak, tamper, debugger detection).
  • RevenueCat handles the cross-platform subscription plumbing you don't want to write.
  • Sentry and Microsoft Clarity give you crash reports and real user-session insight after launch.

How I choose

Before adding a dependency I ask: is it well-maintained, does it have a clear owner, and would it be a nightmare to replace later? Favor small, focused packages that do one thing, keep them behind your own abstractions where you can, and treat every addition as a long-term commitment — because it is.