bloc_plus 0.2.1 copy "bloc_plus: ^0.2.1" to clipboard
bloc_plus: ^0.2.1 copied to clipboard

Extensions for flutter_bloc focused on ergonomics and safety.

bloc_plus #

CI PR Checks pub package license style: flutter_lints codecov

bloc_plus extends flutter_bloc with ergonomic widgets, reusable policies, cooperative async helpers, and explicit effect handling primitives.

Features #

  • BlocBuilderWithBloc, BlocListenerWithBloc, BlocConsumerWithBloc, BlocSelectorWithBloc, BlocConsumerWithEffects
  • BuildContext extensions:
    • readOrNull<B>()
    • watchOrNull<B>()
    • selectOrNull<B, S, T>(selector)
    • withBloc<B, R>(fn)
  • Reusable policies:
    • Rebuild: distinct, onChange, onChangeBy, whenRebuild, always, never
    • Listen: distinctListen, onChangeListen, onChangeListenBy, whenListen, alwaysListen, neverListen
    • Composition: and, or, not
  • Async safety:
    • SafeEmitMixin
    • CancellationToken
    • RestartableTask
    • RestartableTasksMixin
  • Effects:
    • HasEffects
    • EffectListener
    • MultiEffectListener
    • effectWhen filtering

Recent delivery notes are tracked in docs/library_improvement_plan.md.

Getting started #

Add dependency:

dependencies:
  bloc_plus: ^0.2.1

Run the example app:

cd example
flutter pub get
flutter run

Usage #

UI widgets with bloc in callback #

class CounterView extends StatelessWidget {
  const CounterView({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilderWithBloc<CounterCubit, int>(
      builder: (context, bloc, state) {
        return Text('$state');
      },
    );
  }
}

Null-safe context access #

void tryIncrement(BuildContext context) {
  final counterCubit = context.readOrNull<CounterCubit>();
  counterCubit?.increment();
}

Combined state and effects #

class CounterView extends StatelessWidget {
  const CounterView({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocConsumerWithEffects<CounterCubit, CounterState, String>(
      effectWhen: (effect) => effect.startsWith('snack:'),
      listener: (context, bloc, state) {},
      onEffect: (context, bloc, effect) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text(effect)),
        );
      },
      builder: (context, bloc, state) {
        return Text('${state.count}');
      },
    );
  }
}

Policies #

final evenOnlyPolicy = distinct<MyState>().and(
  whenRebuild<MyState>((previous, current) => current.count.isEven),
);

final listPolicy = onChangeBy<MyState, List<int>>(
  (state) => state.items,
  equals: _listEquals,
);

Async safety #

class SearchCubit extends Cubit<SearchState>
    with SafeEmitMixin<SearchState>, RestartableTasksMixin<SearchState> {
  SearchCubit() : super(const SearchState());

  Future<void> loadPreview(String query) async {
    safeEmit(state.copyWith(isLoading: true));

    final result = await runLatest<String>('preview', () async {
      return repository.fetchPreview(query);
    });

    if (result == null) return;
    safeEmit(state.copyWith(isLoading: false, result: result));
  }
}

Effects #

MultiEffectListener(
  listeners: [
    EffectListener<AuthCubit, AuthState, String>(
      effectWhen: (effect) => effect.startsWith('snack:'),
      onEffect: (context, effect) {},
    ),
    EffectListener<AuthCubit, AuthState, String>(
      effectWhen: (effect) => effect.startsWith('dialog:'),
      onEffect: (context, effect) {},
    ),
  ],
  child: const AuthView(),
)
2
likes
160
points
391
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Extensions for flutter_bloc focused on ergonomics and safety.

Repository (GitHub)
View/report issues

Topics

#flutter #bloc #state-management #widget #async

License

MIT (license)

Dependencies

bloc, flutter, flutter_bloc

More

Packages that depend on bloc_plus