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