Line data Source code
1 : import 'package:flutter/material.dart'; 2 : import 'package:meedu/provider.dart'; 3 : import 'package:meedu/state.dart'; 4 : 5 : import '../../utils/ambiguate.dart'; 6 : import '../widgets/watch_filter.dart'; 7 : 8 : typedef _ListenerCallback<T> = void Function(T); 9 : typedef _ProviderListenerCallback<Notifier> = void Function( 10 : BuildContext, 11 : Notifier, 12 : ); 13 : 14 : /// this class allows you listen the changes in multiples providers 15 : class MultiProviderListener extends StatefulWidget { 16 : // ignore: public_member_api_docs 17 1 : MultiProviderListener({ 18 : Key? key, 19 : required this.items, 20 : required this.child, 21 : this.onInitState, 22 : this.onAfterFirstLayout, 23 : this.onDispose, 24 1 : }) : super(key: key); 25 : 26 : /// a list of [MultiProviderListenerItem] 27 : final List<MultiProviderListenerItem> items; 28 : 29 : /// callback when initState is called 30 : final void Function(BuildContext)? onInitState; 31 : 32 : /// this callback will be called when the first frame was rendered 33 : /// use this callback if you want to show a dialog, snackbar or navigate 34 : /// after the first frame 35 : final void Function(BuildContext)? onAfterFirstLayout; 36 : 37 : /// callback when dispose is called 38 : final void Function(BuildContext)? onDispose; 39 : 40 : /// widget to be rendered 41 : final Widget child; 42 : 43 1 : @override 44 1 : _MultiProviderListenerState createState() => _MultiProviderListenerState(); 45 : } 46 : 47 : class _MultiProviderListenerState extends State<MultiProviderListener> { 48 : Map<BaseNotifier, List<_ListenerCallback>> _dependencies = {}; 49 : 50 1 : @override 51 : void initState() { 52 1 : super.initState(); 53 1 : _addListeners(); 54 : // check if the onInitState callback needs to be called 55 2 : if (widget.onInitState != null) { 56 4 : widget.onInitState!(context); 57 : } 58 : 59 : // check if the onAfterFirstLayout callback needs to be called 60 2 : if (widget.onAfterFirstLayout != null) { 61 : // wait after first frame 62 5 : ambiguate(WidgetsBinding.instance)?.endOfFrame.then((_) { 63 1 : if (mounted) { 64 4 : widget.onAfterFirstLayout!(context); 65 : } 66 : }); 67 : } 68 : } 69 : 70 1 : void _addListeners() { 71 : /// read all providers passed into widget.items 72 : /// and add a listener when a notifier changes 73 3 : for (final item in widget.items) { 74 3 : final target = item.provider is Target ? item.provider as Target : null; 75 : final BaseNotifier notifier = target != null 76 1 : ? target.notifier 77 2 : : (item.provider as BaseProvider).read; 78 : 79 2 : if (!_dependencies.containsKey(notifier)) { 80 3 : _dependencies[notifier] = []; 81 : } 82 : 83 : if (target != null) { 84 2 : target.rebuild = () { 85 1 : if (mounted) { 86 : // ignore: avoid_dynamic_calls 87 2 : (item as dynamic).onChange(context, notifier); 88 : } 89 : }; 90 : 91 1 : if (notifier is SimpleNotifier) { 92 2 : if (target.filter == Filter.select) { 93 1 : createSimpleSelectListener(target); 94 : } 95 : } else { 96 2 : if (target.filter == Filter.select) { 97 1 : createStateSelectListener(target); 98 : } else { 99 1 : createWhenListener(target); 100 : } 101 : } 102 1 : final listener = target.listener; 103 1 : (notifier as ListeneableNotifier).addListener(listener); 104 3 : _dependencies[notifier]?.add(listener); 105 : } else { 106 : // ignore: prefer_function_declarations_over_variables 107 1 : final listener = (_) { 108 : // before call onChange we need to check 109 : // if the widget is mounted 110 1 : if (mounted) { 111 : // ignore: avoid_dynamic_calls 112 2 : (item as dynamic).onChange(context, notifier); 113 : } 114 : }; 115 1 : (notifier as ListeneableNotifier).addListener(listener); 116 3 : _dependencies[notifier]?.add(listener); 117 : } 118 : } 119 : } 120 : 121 : /// clear the listeners for this widget 122 1 : void _clearDependencies() { 123 3 : for (final item in _dependencies.entries) { 124 1 : final notifier = item.key; 125 1 : if (!notifier.disposed) { 126 : // ignore: prefer_foreach 127 2 : for (final listener in item.value) { 128 1 : (notifier as ListeneableNotifier).removeListener(listener); 129 : } 130 : } 131 : } 132 2 : _dependencies = {}; 133 : } 134 : 135 : /// listen when the widget is updated 136 : /// due to the properties has changes or for hot reaload 137 1 : @override 138 : void didUpdateWidget(MultiProviderListener oldWidget) { 139 1 : _clearDependencies(); 140 1 : _addListeners(); 141 1 : super.didUpdateWidget(oldWidget); 142 : } 143 : 144 : /// if the widget will be disposed 145 1 : @override 146 : void deactivate() { 147 1 : _clearDependencies(); 148 1 : super.deactivate(); 149 : } 150 : 151 1 : @override 152 : Widget build(BuildContext context) { 153 2 : return widget.child; 154 : } 155 : } 156 : 157 : /// this class is used to define onChange callback for one Notifier 158 : class MultiProviderListenerItem<Notifier extends BaseNotifier> { 159 : // ignore: public_member_api_docs 160 1 : MultiProviderListenerItem({ 161 : required this.provider, 162 : required this.onChange, 163 : }); 164 : 165 : /// provider to listen the changes 166 : final ListeneableProvider<Notifier> provider; 167 : 168 : /// callback to listen the new events 169 : final _ProviderListenerCallback<Notifier> onChange; 170 : }