dartpub.dev DartNative · beta
plugins / flutterbloc_kit
fl

flutterbloc_kit

v0.1.0 MIT

flutter_bloc for DartNative.

BlocProvider, BlocBuilder, BlocListener, BlocConsumer, BlocSelector, RepositoryProvider, the Multi* widgets and context.read / watch / select for DartNative, ported from flutter_bloc on top of package:bloc. Change two imports and a flutter_bloc screen builds.

by edkluivert/flutterbloc_kit · DartNative ≥ 3.0 · updated 5 days ago
Install
Free
pubspec.yaml
dependencies:
  flutterbloc_kit:
    hosted: https://dartpub.dev
    version: ^0.1.0
Weekly installs
0
Active apps
0
Rating
0.0 · 0
Open issues
0

flutterbloc_kit

flutter_bloc for DartNative: the widgets that make the BLoC pattern usable from a widget tree, with the names, type parameters and lifecycle Flutter developers already use, on top of the pure-Dart bloc package.

flutterbloc_kit example: a counter cubit with a toast on every fifth count, rebuild counters for watch / select / BlocSelector, a todos bloc, a settings cubit changing the accent, and a detail route sharing the cubit through BlocProvider.value

Why

bloc is pure Dart and runs on DartNative untouched. flutter_bloc is not: it depends on Flutter's widget layer and on provider, which reaches into the Flutter element tree. This package is that layer rebuilt on the two BuildContext primitives DartNative offers, so a screen written against flutter_bloc ports by changing its imports.

// Before
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// After
import 'package:dartnative/dartnative.dart';
import 'package:flutterbloc_kit/flutterbloc_kit.dart';

package:bloc/bloc.dart is re-exported: Bloc, Cubit, Emitter, BlocObserver, Transition and the transformers need no second import.

What is here

flutter_bloc flutterbloc_kit
BlocProvider, BlocProvider.value, .of same
MultiBlocProvider same
RepositoryProvider, .value, .of same
MultiRepositoryProvider same
BlocBuilder with buildWhen same
BlocListener with listenWhen same
MultiBlocListener same
BlocConsumer same
BlocSelector same
context.read<T>() same
context.watch<T>() same
context.select<T, R>() same
ProviderNotFoundException same
lazy creation, lazy: false, automatic close same

Every widget file in lib/src/ is a port of the flutter_bloc file of the same name (MIT, notice in LICENSE). The provider half is this package's own, see "How it works" below.

Usage

import 'package:dartnative/dartnative.dart';
import 'package:flutterbloc_kit/flutterbloc_kit.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => CounterCubit(),
      child: Scaffold(
        body: Center(
          child: BlocBuilder<CounterCubit, int>(
            builder: (context, count) => Text('$count'),
          ),
        ),
        floatingActionButton: Builder(
          builder: (context) => FloatingActionButton(
            onPressed: () => context.read<CounterCubit>().increment(),
            child: const Icon(CupertinoIcons.plus),
          ),
        ),
      ),
    );
  }
}

Providers compose the way they do in Flutter:

MultiRepositoryProvider(
  providers: [
    RepositoryProvider<TodosRepository>(create: (_) => TodosRepository()),
  ],
  child: MultiBlocProvider(
    providers: [
      BlocProvider<SettingsCubit>(create: (_) => SettingsCubit()),
      BlocProvider<TodosBloc>(
        create: (context) =>
            TodosBloc(context.read<TodosRepository>())..add(const TodosLoaded()),
      ),
    ],
    child: const App(home: HomeScreen()),
  ),
)

Listen, select and consume:

BlocListener<CounterCubit, int>(
  listenWhen: (previous, current) => current % 5 == 0,
  listener: (context, count) => ScaffoldMessenger.of(context)
      .showSnackBar(SnackBar(content: Text('Reached $count'))),
  child: ...,
)

BlocSelector<TodosBloc, TodosState, int>(
  selector: (state) => state.doneCount,
  builder: (context, done) => Text('$done done'),
)

// Inside build: rebuilds when the accent changes, on nothing else.
final accent = context.select<SettingsCubit, Accent>((c) => c.state.accent);

// Inside build: rebuilds on every state the cubit emits.
final settings = context.watch<SettingsCubit>().state;

Share an existing bloc with a pushed route:

Navigator.push(
  context,
  PageRoute(
    builder: (_) => BlocProvider.value(
      value: context.read<CounterCubit>(),
      child: const DetailScreen(),
    ),
  ),
);

The example/ app walks through all of it: a counter cubit with a toast on every fifth count, three rebuild counters that make watch, select and BlocSelector visibly different, a todos bloc fed by a repository, a settings cubit and a shared-cubit detail route.

How it works

DartNative's BuildContext has getInheritedWidgetOfExactType (no dependency) and dependOnInheritedWidgetOfExactType (rebuild when the inherited widget's updateShouldNotify says so), and nothing else from the element tree. provider needs more than that, so:

  • Every provider is a StatefulWidget (InheritedProvider) whose state owns the value: created on first read when lazy, closed or disposed when the provider leaves the tree. Below it sits one InheritedWidget carrying that holder. context.read is the non-depending lookup; .value swaps rebuild dependents through updateShouldNotify.
  • context.watch and context.select on a bloc use the framework's Listenable.watch(context) bridge, which schedules the calling element's rebuild when a listenable notifies. Each (element, bloc) pair gets one Listenable adapting the bloc's state stream: watch notifies on every state, select only when the selected value changes. Registrations are collected per build and committed in a microtask, so nothing leaks across rebuilds.
  • MultiBlocProvider, MultiBlocListener and MultiRepositoryProvider fold their list into nested widgets through SingleChildWidget.withChild, in place of package:nested, which builds Flutter elements directly. A custom entry in one of those lists implements SingleChildWidget.
  • BlocBuilder, BlocListener, BlocConsumer and BlocSelector register a plain dependency on their provider instead of flutter_bloc's context.select<B, bool>((bloc) => identical(_bloc, bloc)); the effect is the same: a BlocProvider.value swap re-subscribes them.

Deviations from flutter_bloc

  • BlocProvider.of and RepositoryProvider.of throw StateError where flutter_bloc throws FlutterError; the message is the same.
  • context.select on a non-bloc value rebuilds when the provided instance is replaced, not on selector changes (provider tracks those through element aspects). For blocs, selection works as in provider.
  • debugFillProperties is not ported; DartNative has no diagnostics tree.
  • SingleChildWidget is this package's interface, not package:nested's.

Testing

Blocs and cubits test with bloc_test as usual. For code that reads them from a BuildContext, package:flutterbloc_kit/testing.dart mounts providers outside a widget tree, through the framework's public state hooks, and hands out a FakeBuildContext:

import 'package:flutterbloc_kit/testing.dart';

test('submit increments through context.read', () {
  final cubit = CounterCubit();
  final providers = mountProviders([
    BlocProvider<CounterCubit>.value(value: cubit),
    RepositoryProvider<Api>.value(value: FakeApi()),
  ]);
  addTearDown(providers.dispose);

  onSubmitPressed(providers.context);

  expect(cubit.state, 1);
});

mountProviders takes the same list a MultiBlocProvider would; create callbacks run lazily and can read providers listed before them, and dispose closes what was created. mountProvider mounts one and lets you update it to test a .value swap. read, watch, BlocProvider.of and RepositoryProvider.of all resolve; nothing rebuilds, since there is no element. The package's own tests are written this way and run with dn test.

Lints

assists_kit 0.1.6 ships three rules for this package: flutterbloc_watch_outside_build and flutterbloc_read_state_in_build (on by default) catch context.watch in an event handler and context.read<T>().state rendered in build, both of which never rebuild; flutterbloc_read_in_build (opt-in) flags every read in build.

License

MIT. Contains code from flutter_bloc and provider (MIT), see LICENSE.