A Minimalistic approach to State Management in Flutter. This is inspired by State Management for Minimalists and tries to put this idea into a reusable component.

Features

Simple State Management based on

Getting started

Create some State class

The state class will be holding some or even all of the apps state. In order to distinguish new from old states, make sure states can be compared for equality.

class SomeState extends Equatable {
  final int counter;

  const SomeState(this.counter);
  @override
  List<Object?> get props => [counter];
}

Define a StateHolder and Service

To separate logic, state and persistence, each State will be handled by a StateHolder class which in turn knows which Service to use for Persistence.

So first define a Service class. This should be the place to talk to backend services or do local persistence on the device.

class BackendService {
  void saveState() {
    // magically save state in some storage
  }

  Future<SomeState> retrieveLastState() async {
    // maybe save it to sqlite or firebase
    return const SomeState(0);
  }
}

Then the StateHolder class to bind it all together. StateHolder class is derived from DefaultStateHolder and typed with the StateClass as well as the Service classname.

Stateholder

  1. holds the state
  2. provides mutation methods to be used anywhere
  3. knows how to notify rendering widgets of changes
class CounterStateHolder extends DefaultStateHolder<SomeState, BackendService> {
  CounterStateHolder(SomeState value) : super(value);

  // define mutation methods
  void increment() {
    // create new state derived from old value/state
    var nextState = SomeState(value.counter + 1);
    
    // using backendService to save State somewhere
    getService().saveState(nextState);
    
    // calling setState triggers re rendering
    setState(nextState);
  }


  void decrement() {
    // create new state derive from old value/state
    // calling setState triggers re rendering
    setState(SomeState(value.counter - 1));
  }
}

Usage

Once all needed classes are set up, make sure to register all StateHolder with their Serives at start of the main app.


void main() {
  // register all StateHolders and their needed Services before
  // Start of the App
  registerState(CounterStateHolder(const SomeState(0)), BackendService());

  // and finally run the app
  runApp(const MyApp());
}

To display states and react on state changes use

// access Stateholder anywhere in the code 
// using provided stateHolder<> method
var stateholder = stateHolder<CounterStateHolder>();
...
MiniStateBuilder<CounterStateHolder, SomeState>(
        listener: (context, value) {
            // react here on specific states just before rendering the
            // new state
            if (value.counter == 10) {
                // reset by explicitly setting states value
                // told ya. minimalistic approach.
                stateholder.setState(const SomeState(0));
            }
        }, 
        builder: (ctx, value, stateHolder, child) {
            return Text(
            '${value.counter}',
            style: Theme.of(context).textTheme.headline4,
            );
        })
...

As the StateHolder is available anywhere using LocatorPattern, one can simply use it in any action handler to mutate state.

ElevatedButton(
    onPressed: () {
    stateHolder<CounterStateHolder>().decrement();
    },
    child: const Text("-"),
),

Libraries

default_state_holder
mini_state_builder
ministate
state
state_holder
widget