flowdux_flutter 0.2.4 copy "flowdux_flutter: ^0.2.4" to clipboard
flowdux_flutter: ^0.2.4 copied to clipboard

Flutter bindings for FlowDux state management library. Provides StoreProvider, StoreBuilder, and StoreConsumer widgets.

FlowDux Flutter #

Flutter bindings for FlowDux state management library.

pub package License: Apache 2.0

Features #

  • StoreProvider - Provide store to widget tree via InheritedWidget
  • StoreBuilder - Rebuild widgets when state changes
  • StoreSelector - Optimized rebuilds with selectors
  • StoreConsumer - Access both store and state
  • StoreListener - Side effects on state changes

Installation #

dependencies:
  flowdux: ^0.2.4
  flowdux_flutter: ^0.2.4

Quick Start #

1. Wrap Your App with StoreProvider #

void main() {
  final store = createStore<AppState, AppAction>(
    initialState: AppState(),
    reducer: appReducer,
    middlewares: [AppMiddleware()],
  );

  runApp(
    StoreProvider<AppState, AppAction>(
      store: store,
      child: MyApp(),
    ),
  );
}

2. Use StoreBuilder for Reactive UI #

class CounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreBuilder<AppState, AppAction>(
      builder: (context, state) {
        return Text('Count: ${state.count}');
      },
    );
  }
}

3. Dispatch Actions #

// Using context extension
ElevatedButton(
  onPressed: () => context.dispatch<AppState, AppAction>(IncrementAction()),
  child: Text('Increment'),
)

// Or access the store directly
ElevatedButton(
  onPressed: () {
    final store = StoreProvider.of<AppState, AppAction>(context);
    store.dispatch(IncrementAction());
  },
  child: Text('Increment'),
)

Widgets #

StoreProvider #

Provides a store to all descendant widgets using InheritedWidget.

StoreProvider<AppState, AppAction>(
  store: store,
  child: MyApp(),
)

// Access the store
final store = StoreProvider.of<AppState, AppAction>(context);

// Or use the extension
final store = context.store<AppState, AppAction>();

// Safe access (returns null if not found)
final store = StoreProvider.maybeOf<AppState, AppAction>(context);

StoreBuilder #

Rebuilds its child when the store's state changes.

StoreBuilder<AppState, AppAction>(
  builder: (context, state) {
    return Column(
      children: [
        Text('Count: ${state.count}'),
        Text('Name: ${state.name}'),
      ],
    );
  },
)

You can also pass a store directly:

StoreBuilder<AppState, AppAction>(
  store: myStore, // Optional, uses provider if not specified
  builder: (context, state) {
    return Text('Count: ${state.count}');
  },
)

StoreSelector #

Optimized widget that only rebuilds when the selected value changes.

StoreSelector<AppState, AppAction, int>(
  selector: (state) => state.count,
  builder: (context, count) {
    return Text('Count: $count');
  },
)

// Complex selector
StoreSelector<AppState, AppAction, String>(
  selector: (state) => '${state.firstName} ${state.lastName}',
  builder: (context, fullName) {
    return Text('Name: $fullName');
  },
)

StoreConsumer #

Combines StoreBuilder with store access for dispatching actions.

StoreConsumer<AppState, AppAction>(
  builder: (context, store, state) {
    return Column(
      children: [
        Text('Count: ${state.count}'),
        ElevatedButton(
          onPressed: () => store.dispatch(IncrementAction()),
          child: Text('Increment'),
        ),
      ],
    );
  },
)

With listener for side effects:

StoreConsumer<AppState, AppAction>(
  listener: (context, store, state) {
    if (state.error != null) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.error!)),
      );
    }
  },
  builder: (context, store, state) {
    return MyWidget(state: state);
  },
)

StoreListener #

Listens to state changes without rebuilding. Use for side effects like navigation or showing dialogs.

StoreListener<AppState, AppAction>(
  listener: (context, store, state) {
    if (state.shouldNavigate) {
      Navigator.of(context).pushNamed('/next');
    }
  },
  child: MyWidget(),
)

With conditional listening:

StoreListener<AppState, AppAction>(
  listenWhen: (previous, current) => previous.route != current.route,
  listener: (context, store, state) {
    Navigator.of(context).pushNamed(state.route);
  },
  child: MyWidget(),
)

Complete Example #

import 'package:flutter/material.dart';
import 'package:flowdux_flutter/flowdux_flutter.dart';

// State
class CounterState {
  final int count;
  CounterState({this.count = 0});
  CounterState copyWith({int? count}) =>
      CounterState(count: count ?? this.count);
}

// Actions
class IncrementAction implements Action {}
class DecrementAction implements Action {}

// Reducer
final counterReducer = buildReducer<CounterState, Action>((b) {
  b.on<IncrementAction>((state, _) => state.copyWith(count: state.count + 1));
  b.on<DecrementAction>((state, _) => state.copyWith(count: state.count - 1));
});

void main() {
  final store = createStore<CounterState, Action>(
    initialState: CounterState(),
    reducer: counterReducer,
  );

  runApp(
    StoreProvider<CounterState, Action>(
      store: store,
      child: MaterialApp(
        home: CounterPage(),
      ),
    ),
  );
}

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: StoreBuilder<CounterState, Action>(
          builder: (context, state) {
            return Text(
              '${state.count}',
              style: Theme.of(context).textTheme.headlineLarge,
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          FloatingActionButton(
            onPressed: () =>
                context.dispatch<CounterState, Action>(IncrementAction()),
            child: Icon(Icons.add),
          ),
          SizedBox(height: 8),
          FloatingActionButton(
            onPressed: () =>
                context.dispatch<CounterState, Action>(DecrementAction()),
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

Testing #

testWidgets('counter increments', (tester) async {
  final store = createStore<CounterState, Action>(
    initialState: CounterState(),
    reducer: counterReducer,
  );

  await tester.pumpWidget(
    MaterialApp(
      home: StoreProvider<CounterState, Action>(
        store: store,
        child: CounterPage(),
      ),
    ),
  );

  expect(find.text('0'), findsOneWidget);

  await tester.tap(find.byIcon(Icons.add));
  await tester.runAsync(() => Future.delayed(Duration(milliseconds: 50)));
  await tester.pumpAndSettle();

  expect(find.text('1'), findsOneWidget);
});

API Reference #

See the API documentation for complete details.

License #

Apache License 2.0 - see LICENSE for details.

0
likes
160
points
188
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter bindings for FlowDux state management library. Provides StoreProvider, StoreBuilder, and StoreConsumer widgets.

Repository (GitHub)
View/report issues

Documentation

Documentation
API reference

License

Apache-2.0 (license)

Dependencies

flowdux, flutter

More

Packages that depend on flowdux_flutter