Skip to content

Quick Decision Guide

You need to...Use this package
Access services/dependencies anywhere in your appget_it
Update UI automatically when data changeswatch_it + get_it
Handle async actions with loading/error statescommand_it
Transform, combine reactive data or use observable collectionslisten_it

Why these packages?

Good Flutter architecture follows key principles: separation of concerns, single source of truth, and testability. The flutter_it packages help you implement these principles without the complexity of traditional frameworks.

💡 New to Flutter architecture? Jump to detailed architecture principles to understand the foundation.

flutter_it Architecture

flutter_it Architecture


The Problems Each Package Solves

get_itget_it - Access anything, anywhere

Problem: How do I access services, business logic, and shared data without passing it through the widget tree?

Solution: Service locator pattern - register once, access anywhere without BuildContext.

Use when:

  • You need dependency injection without the widget tree
  • You want to share services across your app
  • You need control over object lifecycle (singletons, factories, scopes)
  • You want to test your business logic independently

Example use case: Accessing an API service from anywhere in your app.

get_it Data Flow

Get started with get_it →


watch_itwatch_it - Reactive UI updates

Problem: How do I update my UI when data changes without setState() or complex state management?

Solution: Watch ValueListenable/ChangeNotifier and rebuild automatically - you'll almost never need StatefulWidget again.

Use when:

  • You want automatic UI updates on data changes
  • You want to eliminate StatefulWidget and setState() boilerplate
  • You need fine-grained rebuilds (only affected widgets)
  • You're tired of manually managing subscriptions

Example use case: A counter widget that rebuilds when the count changes.

Requires: get_it for service location

watch_it Data Flow

Get started with watch_it →


command_itcommand_it - Smart action encapsulation

Problem: How do I handle async operations with loading states, errors, and enable/disable logic without repetitive boilerplate?

Solution: Command pattern with built-in state management - handle exceptions the smart way.

Use when:

  • You have async operations (API calls, database operations)
  • You need loading indicators and error handling
  • You want to enable/disable actions based on conditions
  • You want reusable, testable action logic

Example use case: A save button that shows loading state, handles errors, and can be disabled.

command_it Data Flow

Get started with command_it →


listen_itlisten_it - Reactive primitives

Problem: How do I transform, combine, filter reactive data? How do I make collections observable?

Solution: RxDart-like operators for ValueNotifier that are easy to understand, plus reactive collections (ListNotifier, MapNotifier, SetNotifier).

Use when:

  • You need to transform ValueListenable data (map, where, debounce)
  • You need to combine multiple ValueListenables into one
  • You want observable Lists, Maps, or Sets that notify on changes
  • You need reactive data pipelines without RxDart complexity

Example use case: Debouncing search input, or a shopping cart that notifies on item changes.

listen_it Data Flow

Get started with listen_it →


Common Package Combinations

Minimal Setup: get_it + watch_it

Perfect for apps that need dependency injection and reactive UI. Covers 90% of typical app needs.

Example: Most CRUD apps, dashboard apps, form-heavy apps.

Full Stack: All 4 packages

Complete reactive architecture with dependency injection, reactive UI, command pattern, and data transformations.

Example: Complex apps with API integration, real-time updates, and sophisticated state transformations.

Standalone Use Cases

Each package works independently:

  • Just get_it: Simple dependency injection without reactivity
  • Just listen_it: Reactive operators/collections without dependency injection
  • Just command_it: Command pattern for encapsulating actions

Architecture Principles

Why separate your code into layers?

flutter_it packages enable clean architecture by solving specific problems that arise when you separate concerns:

The Goal: Keep business logic separate from UI, maintain a single source of truth, make everything testable.

The Challenge: Once you move data out of widgets, you need:

  1. A way to access that data from anywhere → get_it solves this
  2. A way to update UI when data changes → watch_it solves this
  3. A way to handle async operations cleanly → command_it solves this
  4. A way to transform and combine reactive data → listen_it solves this

Separation of concerns

Different parts of your application should have different responsibilities:

  • UI layer: Displays data and handles user interactions
  • Business logic layer: Processes data and implements app rules
  • Services layer: Communicates with external systems (APIs, databases, device features)

By separating these layers, you can:

  • Change one part without affecting others
  • Test business logic without UI
  • Reuse logic across different screens

Single source of truth

Each piece of data should live in exactly one place. If you have a list of users, there should be one list, not multiple copies across different widgets.

Benefits:

  • Data stays consistent across your app
  • Updates happen in one place
  • Easier to debug and maintain

Testability

Your app should be designed for easy testing:

  • Business logic can be tested without Flutter widgets
  • Services can be mocked for unit tests
  • UI can be tested independently with test data

For a comprehensive discussion, see Practical Flutter Architecture.


Next Steps

Ready to start? Pick your first package:

Not sure yet? Check out real-world examples to see the packages in action.

Released under the MIT License.