Flutter-State management

George Ma
6 min readMar 3, 2024

--

Flutter-State management

StatefulWidget lifecycle

Provider

class InheritedProvider<T> extends InheritedWidget {
InheritedProvider({
required this.data,
required Widget child,
}) : super(child: child);

final T data;

@override
bool updateShouldNotify(InheritedProvider<T> old) {
return true;
}
}
class ChangeNotifier implements Listenable {
List listeners=[];
@override
void addListener(VoidCallback listener) {
listeners.add(listener);
}
@override
void removeListener(VoidCallback listener) {
listeners.remove(listener);
}

void notifyListeners() {
listeners.forEach((item)=>item());
}

//...
}
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
ChangeNotifierProvider({
Key? key,
this.data,
this.child,
});

final Widget child;
final T data;

static T of<T>(BuildContext context, {bool listen = true}) {
final type = _typeOf<InheritedProvider<T>>();
final provider = listen
? context.dependOnInheritedWidgetOfExactType<InheritedProvider<T>>()
: context.getElementForInheritedWidgetOfExactType<InheritedProvider<T>>()?.widget
as InheritedProvider<T>;
return provider.data;
}

@override
_ChangeNotifierProviderState<T> createState() => _ChangeNotifierProviderState<T>();
}

class _ChangeNotifierProviderState<T extends ChangeNotifier> extends State<ChangeNotifierProvider<T>> {
void update() {
setState(() => {});
}

@override
void didUpdateWidget(ChangeNotifierProvider<T> oldWidget) {
if (widget.data != oldWidget.data) {
oldWidget.data.removeListener(update);
widget.data.addListener(update);
}
super.didUpdateWidget(oldWidget);
}

@override
void initState() {
widget.data.addListener(update);
super.initState();
}

@override
void dispose() {
widget.data.removeListener(update);
super.dispose();
}

@override
Widget build(BuildContext context) {
return InheritedProvider<T>(
data: widget.data,
child: widget.child,
);
}
}
class Consumer<T> extends StatelessWidget {
Consumer({
Key? key,
required this.builder,
}) : super(key: key);

final Widget Function(BuildContext context, T? value) builder;

@override
Widget build(BuildContext context) {
return builder(
context,
ChangeNotifierProvider.of<T>(context),
);
}
}

Bloc

BlocProvider

class BlocProvider<T extends BlocBase<Object?>>
extends SingleChildStatelessWidget with BlocProviderSingleChildWidget {
/// {@macro bloc_provider}
BlocProvider({
Key? key,
required Create<T> create,
this.child,
this.lazy,
}) : _create = create,
_value = null,
super(key: key, child: child);

BlocProvider.value({
Key? key,
required T value,
this.child,
}) : _value = value,
_create = null,
lazy = null,
super(key: key, child: child);

/// Widget which will have access to the [Bloc] or [Cubit].
final Widget? child;

final bool? lazy;

final Create<T>? _create;

final T? _value;

static T of<T extends BlocBase<Object?>>(
BuildContext context, {
bool listen = false,
}) {
try {
return Provider.of<T>(context, listen: listen);
} on ProviderNotFoundException catch (e) {
if (e.valueType != T) rethrow;
throw FlutterError(
'''
BlocProvider.of() called with a context that does not contain a $T.
No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>().
This can happen if the context you used comes from a widget above the BlocProvider.
The context used was: $context
''',
);
}
}

@override
Widget buildWithChild(BuildContext context, Widget? child) {
final value = _value;
return value != null
? InheritedProvider<T>.value(
value: value,
startListening: _startListening,
lazy: lazy,
child: child,
)
: InheritedProvider<T>(
create: _create,
dispose: (_, bloc) => bloc.close(),
startListening: _startListening,
child: child,
lazy: lazy,
);
}

static VoidCallback _startListening(
InheritedContext<BlocBase> e,
BlocBase value,
) {
final subscription = value.stream.listen(
(dynamic _) => e.markNeedsNotifyDependents(),
);
return subscription.cancel;
}
}

BlocBase

abstract class BlocBase<State> {
BlocBase(this._state) {
Bloc.observer.onCreate(this);
}

StreamController<State>? __stateController;
StreamController<State> get _stateController {
return __stateController ??= StreamController<State>.broadcast();
}

State _state;

bool _emitted = false;

State get state => _state;

Stream<State> get stream => _stateController.stream;

@Deprecated(
'Use stream.listen instead. Will be removed in v8.0.0',
)
StreamSubscription<State> listen(
void Function(State)? onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
return stream.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}

void emit(State state) {
if (_stateController.isClosed) return;
if (state == _state && _emitted) return;
onChange(Change<State>(currentState: this.state, nextState: state));
_state = state;
_stateController.add(_state);
_emitted = true;
}

@mustCallSuper
void onChange(Change<State> change) {
Bloc.observer.onChange(this, change);
}

@mustCallSuper
void addError(Object error, [StackTrace? stackTrace]) {
onError(error, stackTrace ?? StackTrace.current);
}

@protected
@mustCallSuper
void onError(Object error, StackTrace stackTrace) {
Bloc.observer.onError(this, error, stackTrace);
assert(() {
throw BlocUnhandledErrorException(this, error, stackTrace);
}());
}

@mustCallSuper
Future<void> close() async {
Bloc.observer.onClose(this);
await _stateController.close();
}
}

BlocBuilderBase

abstract class BlocBuilderBase<B extends BlocBase<S>, S>
extends StatefulWidget {
const BlocBuilderBase({Key? key, this.bloc, this.buildWhen})
: super(key: key);

final B? bloc;

final BlocBuilderCondition<S>? buildWhen;

Widget build(BuildContext context, S state);

@override
State<BlocBuilderBase<B, S>> createState() => _BlocBuilderBaseState<B, S>();
}

class _BlocBuilderBaseState<B extends BlocBase<S>, S>
extends State<BlocBuilderBase<B, S>> {
late B _bloc;
late S _state;

@override
void initState() {
super.initState();
_bloc = widget.bloc ?? context.read<B>();
_state = _bloc.state;
}

...

@override
Widget build(BuildContext context) {
...
return BlocListener<B, S>(
bloc: _bloc,
listenWhen: widget.buildWhen,
listener: (context, state) => setState(() => _state = state),
child: widget.build(context, _state),
);
}
}

BlocListenerBase

abstract class BlocListenerBase<B extends BlocBase<S>, S>
extends SingleChildStatefulWidget {
const BlocListenerBase({
Key? key,
required this.listener,
this.bloc,
this.child,
this.listenWhen,
}) : super(key: key, child: child);

final Widget? child;

final B? bloc;

final BlocWidgetListener<S> listener;

final BlocListenerCondition<S>? listenWhen;

@override
SingleChildState<BlocListenerBase<B, S>> createState() =>
_BlocListenerBaseState<B, S>();
}

class _BlocListenerBaseState<B extends BlocBase<S>, S>
extends SingleChildState<BlocListenerBase<B, S>> {
StreamSubscription<S>? _subscription;
late B _bloc;
late S _previousState;

@override
void initState() {
super.initState();
_bloc = widget.bloc ?? context.read<B>();
_previousState = _bloc.state;
_subscribe();
}

//...

@override
Widget buildWithChild(BuildContext context, Widget? child) {
return child!;
}

@override
void dispose() {
_unsubscribe();
super.dispose();
}

void _subscribe() {
_subscription = _bloc.stream.listen((state) {
if (widget.listenWhen?.call(_previousState, state) ?? true) {
widget.listener(context, state);
}
_previousState = state;
});
}

void _unsubscribe() {
_subscription?.cancel();
_subscription = null;
}
}

Redux

StoreProvider

const StoreProvider({
Key key,
@required Store<S> store,
@required Widget child,
}) : assert(store != null),
assert(child != null),
_store = store,
super(key: key, child: child);
//...
@override
bool updateShouldNotify(StoreProvider<S> oldWidget) =>
_store != oldWidget._store;

static Store<S> of<S>(BuildContext context, {bool listen = true}) {
final type = _typeOf<StoreProvider<S>>();
final provider = (listen
? context.inheritFromWidgetOfExactType(type)
: context
.ancestorInheritedElementForWidgetOfExactType(type)
?.widget) as StoreProvider<S>;

if (provider == null) throw StoreProviderError(type);

return provider._store;
}

Store

class Store<State> {

Reducer<State> reducer;
final StreamController<State> _changeController;
State _state;
List<NextDispatcher> _dispatchers;

Store(
this.reducer, {
State initialState,
List<Middleware<State>> middleware = const [],
bool syncStream = false,
bool distinct = false,
}) :
_changeController = StreamController.broadcast(sync: syncStream) {
_state = initialState;
_dispatchers = _createDispatchers(
middleware,
_createReduceAndNotify(distinct),
);
}

typedef dynamic NextDispatcher(dynamic action);

List<NextDispatcher> _createDispatchers(
List<Middleware<State>> middleware,
NextDispatcher reduceAndNotify,
) {
final dispatchers = <NextDispatcher>[]..add(reduceAndNotify);

for (var nextMiddleware in middleware.reversed) {
final next = dispatchers.last;

dispatchers.add(
(dynamic action) => nextMiddleware(this, action, next),
);
}
return dispatchers.reversed.toList();
}

NextDispatcher _createReduceAndNotify(bool distinct) {
return (dynamic action) {
final state = reducer(_state, action);

if (distinct && state == _state) return;

_state = state;
_changeController.add(state);
};
}

StoreConnector

@override
Widget build(BuildContext context) {
return _StoreStreamListener<S, ViewModel>(
store: StoreProvider.of<S>(context),
builder: builder,
converter: converter,
distinct: distinct,
onInit: onInit,
onDispose: onDispose,
rebuildOnChange: rebuildOnChange,
ignoreChange: ignoreChange,
onWillChange: onWillChange,
onDidChange: onDidChange,
onInitialBuild: onInitialBuild,
);
}
class _StoreStreamListenerState<S, ViewModel>
extends State<_StoreStreamListener<S, ViewModel>> {
late Stream<ViewModel> _stream;
ViewModel? _latestValue;
ConverterError? _latestError;

// `_latestValue!` would throw _CastError if `ViewModel` is nullable,
// therefore `_latestValue as ViewModel` is used.
// https://dart.dev/null-safety/understanding-null-safety#nullability-and-generics
ViewModel get _requireLatestValue => _latestValue as ViewModel;

@override
void initState() {
widget.onInit?.call(widget.store);

_computeLatestValue();

if (widget.onInitialBuild != null) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
widget.onInitialBuild!(_requireLatestValue);
});
}

_createStream();

super.initState();
}

@override
void dispose() {
widget.onDispose?.call(widget.store);

super.dispose();
}

@override
void didUpdateWidget(_StoreStreamListener<S, ViewModel> oldWidget) {
_computeLatestValue();

if (widget.store != oldWidget.store) {
_createStream();
}

super.didUpdateWidget(oldWidget);
}

void _computeLatestValue() {
try {
_latestError = null;
_latestValue = widget.converter(widget.store);
} catch (e, s) {
_latestValue = null;
_latestError = ConverterError(e, s);
}
}

@override
Widget build(BuildContext context) {
return widget.rebuildOnChange
? StreamBuilder<ViewModel>(
stream: _stream,
builder: (context, snapshot) {
if (_latestError != null) throw _latestError!;

return widget.builder(
context,
_requireLatestValue,
);
},
)
: _latestError != null
? throw _latestError!
: widget.builder(context, _requireLatestValue);
}

ViewModel _mapConverter(S state) {
return widget.converter(widget.store);
}

bool _whereDistinct(ViewModel vm) {
if (widget.distinct) {
return vm != _latestValue;
}

return true;
}

bool _ignoreChange(S state) {
if (widget.ignoreChange != null) {
return !widget.ignoreChange!(widget.store.state);
}

return true;
}

void _createStream() {
_stream = widget.store.onChange
.where(_ignoreChange)
.map(_mapConverter)
// Don't use `Stream.distinct` because it cannot capture the initial
// ViewModel produced by the `converter`.
.where(_whereDistinct)
// After each ViewModel is emitted from the Stream, we update the
// latestValue. Important: This must be done after all other optional
// transformations, such as ignoreChange.
.transform(StreamTransformer.fromHandlers(
handleData: _handleChange,
handleError: _handleError,
));
}

void _handleChange(ViewModel vm, EventSink<ViewModel> sink) {
_latestError = null;
widget.onWillChange?.call(_latestValue, vm);
final previousValue = vm;
_latestValue = vm;

if (widget.onDidChange != null) {
WidgetsBinding.instance?.addPostFrameCallback((_) {
if (mounted) {
widget.onDidChange!(previousValue, _requireLatestValue);
}
});
}

sink.add(vm);
}

void _handleError(
Object error,
StackTrace stackTrace,
EventSink<ViewModel> sink,
) {
_latestValue = null;
_latestError = ConverterError(error, stackTrace);
sink.addError(error, stackTrace);
}
}

--

--