Line data Source code
1 : import 'dart:async'; 2 : 3 : import 'package:flutter/foundation.dart'; 4 : import 'package:flutter/widgets.dart' show BuildContext; 5 : import 'package:flutter_hooks/flutter_hooks.dart'; 6 : 7 : import 'flutter_bloc.dart' show Cubit, Bloc, BlocBase, ReadContext; 8 : 9 : /// Signature for the `listener` function which takes the `BuildContext` along 10 : /// with the `current` and `previous` state and is responsible for executing in 11 : /// response to `state` changes. 12 : typedef BlocHookListener<S> = bool Function( 13 : BuildContext context, 14 : S previous, 15 : S current, 16 : ); 17 : 18 : /// {@template BlocWidget} 19 : /// The [BlocWidget] is the base for every reimplementation of `flutter_bloc`'s 20 : /// widgets based on `Hook`s. 21 : /// {@endtemplate} 22 : abstract class BlocWidget<B extends BlocBase<S>, S extends Object> 23 : extends HookWidget { 24 : /// {@macro BlocWidget} 25 4 : const BlocWidget({ 26 : Key? key, 27 : this.bloc, 28 4 : }) : super(key: key); 29 : 30 : /// `bloc` that has the state. If it's null, it will be infered from 31 : /// [BuildContext] 32 : final B? bloc; 33 : 34 : /// The `$use` method is a sugar syntax for the `useBloc`. 35 4 : @protected 36 12 : B $use() => useBloc<B, S>(bloc: bloc, onEmitted: onStateEmitted); 37 : 38 : /// The `onStateEmitted` method allows to customize the behavior 39 : /// of the implementation of the [BlocWidget] 40 : @protected 41 : bool onStateEmitted(BuildContext context, S previous, S state); 42 : } 43 : 44 : /// Subscribes to a Cubit and handles a listener or a rebuild. 45 : /// 46 : /// Whenever [BlocBase.state] updates, it will mark the caller [HookWidget] 47 : /// as needing build if either `allowRebuild` is `true` or `buildWhen` 48 : /// invocation returns `true`. 49 : /// 50 : /// if [bloc] is null, it will be inherited with `context.bloc()` 51 : /// 52 : /// The following example showcase a basic counter application. 53 : /// 54 : /// ```dart 55 : /// class CounterCubit extends Cubit<int> { 56 : /// CounterCubit() : super(0); 57 : /// 58 : /// void increment() => emit(state + 1); 59 : /// } 60 : /// 61 : /// class Counter extends HookWidget { 62 : /// @override 63 : /// Widget build(BuildContext context) { 64 : /// // automatically triggers a rebuild of Counter widget 65 : /// final counterCubit = useBloc<CounterCubit, int>(); 66 : /// 67 : /// return GestureDetector( 68 : /// onTap: () => counterCubit.increment(), 69 : /// child: Text('${counter.state}'), 70 : /// ); 71 : /// } 72 : /// } 73 : /// ``` 74 : /// 75 : /// See also: 76 : /// 77 : /// * [Cubit] 78 : /// * [Bloc] 79 : /// * [BlocBase] 80 4 : B useBloc<B extends BlocBase<S>, S extends Object>({ 81 : B? bloc, 82 : BlocHookListener<S>? onEmitted, 83 : }) { 84 4 : final _bloc = bloc ?? useContext().read<B>(); 85 8 : return use(_BlocHook<S>(_bloc, onEmitted)) as B; 86 : } 87 : 88 : class _BlocHook<S> extends Hook<BlocBase<S>> { 89 4 : const _BlocHook(this.bloc, this.onEmitted); 90 : 91 : final BlocBase<S> bloc; 92 : final BlocHookListener<S>? onEmitted; 93 : 94 4 : @override 95 4 : HookState<BlocBase<S>, _BlocHook<S>> createState() => _BlocHookState<S>(); 96 : } 97 : 98 : class _BlocHookState<S> extends HookState<BlocBase<S>, _BlocHook<S>> { 99 : // ignore: cancel_subscriptions 100 : StreamSubscription<S>? _subscription; 101 : 102 : /// Previous state from cubit 103 : late S _previous; 104 : 105 4 : @override 106 8 : BlocBase<S> build(BuildContext context) => hook.bloc; 107 : 108 4 : @override 109 : void initHook() { 110 4 : super.initHook(); 111 16 : _previous = hook.bloc.state; 112 4 : _subscribe(); 113 : } 114 : 115 3 : @override 116 : void didUpdateHook(_BlocHook<S> oldHook) { 117 3 : super.didUpdateHook(oldHook); 118 3 : final oldCubit = oldHook.bloc; 119 6 : final currentCubit = hook.bloc; 120 3 : if (oldCubit != currentCubit) { 121 2 : if (_subscription != null) { 122 2 : _unsubscribe(); 123 8 : _previous = hook.bloc.state; 124 : } 125 2 : _subscribe(); 126 : } 127 : } 128 : 129 4 : @override 130 : void dispose() { 131 4 : _unsubscribe(); 132 4 : super.dispose(); 133 : } 134 : 135 4 : void _subscribe() { 136 24 : _subscription = hook.bloc.stream.listen((state) { 137 20 : if (hook.onEmitted?.call(context, _previous, state) ?? true) { 138 4 : setState(() {}); 139 : } 140 4 : _previous = state; 141 : }); 142 : } 143 : 144 4 : void _unsubscribe() { 145 4 : if (_subscription != null) { 146 8 : _subscription!.cancel(); 147 4 : _subscription = null; 148 : } 149 : } 150 : }