diff --git a/example/lib/main_is_waiting_works_when_multiple_actions.dart b/example/lib/main_is_waiting_works_when_multiple_actions.dart new file mode 100644 index 0000000..50a5b99 --- /dev/null +++ b/example/lib/main_is_waiting_works_when_multiple_actions.dart @@ -0,0 +1,169 @@ +import 'package:async_redux/async_redux.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + var store = Store(initialState: AppState(counter: 0)); + store.onChange.listen(print); + + return MaterialApp( + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: StoreProvider( + store: store, + child: const MyHomePage(), + ), + ); + } +} + +class MyHomePage extends StatelessWidget { + const MyHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return StoreConnector( + vm: () => CounterVmFactory(), + shouldUpdateModel: (s) => s.counter >= 0, + builder: (context, vm) { + return MyHomePageContent( + title: 'IsWaiting works when multiple actions', + counter: vm.counter, + isCalculating: vm.isCalculating, + increment: vm.increment, + multiply: vm.multiply, + ); + }, + ); + } +} + +class MyHomePageContent extends StatelessWidget { + const MyHomePageContent({ + super.key, + required this.title, + required this.counter, + required this.isCalculating, + required this.increment, + required this.multiply, + }); + + final String title; + final int counter; + final bool isCalculating; + final VoidCallback increment, multiply; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text(title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Result:'), + Text( + '$counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: Column( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + onPressed: isCalculating ? null : increment, + elevation: isCalculating ? 0 : 6, + backgroundColor: isCalculating ? Colors.grey[300] : Colors.blue, + child: isCalculating + ? const Padding( + padding: const EdgeInsets.all(16.0), + child: const CircularProgressIndicator(), + ) + : const Icon(Icons.add), + ), + const SizedBox(height: 16), + FloatingActionButton( + onPressed: isCalculating ? null : multiply, + elevation: isCalculating ? 0 : 6, + backgroundColor: isCalculating ? Colors.grey[300] : Colors.blue, + child: isCalculating + ? const Padding( + padding: const EdgeInsets.all(16.0), + child: const CircularProgressIndicator(), + ) + : const Icon(Icons.close), + ) + ], + ), + ); + } +} + +class AppState { + final int counter; + + AppState({required this.counter}); + + AppState copy({int? counter}) => AppState(counter: counter ?? this.counter); + + @override + String toString() { + return '.\n.\n.\nAppState{counter: $counter}\n.\n.\n'; + } +} + +class CounterVm extends Vm { + final int counter; + final bool isCalculating; + final VoidCallback increment, multiply; + + CounterVm({ + required this.counter, + required this.isCalculating, + required this.increment, + required this.multiply, + }) : super(equals: [ + counter, + isCalculating, + ]); +} + +class CounterVmFactory extends VmFactory { + @override + CounterVm fromStore() => CounterVm( + counter: state.counter, + isCalculating: isWaiting([IncrementAction, MultiplyAction]), + increment: () => dispatch(IncrementAction()), + multiply: () => dispatch(MultiplyAction()), + ); +} + +class IncrementAction extends ReduxAction { + @override + Future reduce() async { + await Future.delayed(const Duration(seconds: 1)); + return AppState(counter: state.counter + 1); + } +} + +class MultiplyAction extends ReduxAction { + @override + Future reduce() async { + await Future.delayed(const Duration(seconds: 1)); + return AppState(counter: state.counter * 2); + } +} diff --git a/lib/src/store.dart b/lib/src/store.dart index 2e36386..894a38e 100644 --- a/lib/src/store.dart +++ b/lib/src/store.dart @@ -1581,22 +1581,24 @@ class Store { // 3) If an iterable was passed: // 3.1) For each action or action type in the iterable... else if (actionOrActionTypeOrList is Iterable) { + bool isWaiting = false; for (var actionOrType in actionOrActionTypeOrList) { // // 3.2) If it's a type. if (actionOrType is Type) { _awaitableActions.add(actionOrType); - // 3.2.1) Return true if any of the actions in progress has that exact type. - return _actionsInProgress.any((action) => action.runtimeType == actionOrType); + // 3.2.1) Is waiting if any of the actions in progress has that exact type. + if (!isWaiting) + isWaiting = _actionsInProgress.any((action) => action.runtimeType == actionOrType); } // // 3.3) If it's an action. else if (actionOrType is ReduxAction) { _awaitableActions.add(actionOrType.runtimeType); - // 3.3.1) Return true if any of the actions in progress is the exact action. - return _actionsInProgress.contains(actionOrType); + // 3.3.1) Is waiting if any of the actions in progress is the exact action. + if (!isWaiting) isWaiting = _actionsInProgress.contains(actionOrType); } // // 3.4) If it's not an action and not an action type, throw an exception. @@ -1604,21 +1606,21 @@ class Store { else { Future.microtask(() { throw StoreException( - "You can't do isWaiting([${actionOrActionTypeOrList.runtimeType}]), " - "but only an action Type, a ReduxAction, or a List of them."); + "You can't do isWaiting([${actionOrActionTypeOrList.runtimeType}]). " + "Use only actions, action types, or a list of them."); }); } } // 3.5) If the `for` finished without matching any items, return false (it's NOT waiting). - return false; + return isWaiting; } // 4) If something different was passed, it's an error. We show the error after the // async gap, so we don't interrupt the code. But we return false (not waiting). else { Future.microtask(() { throw StoreException("You can't do isWaiting(${actionOrActionTypeOrList.runtimeType}), " - "but only an action Type, a ReduxAction, or a List of them."); + "Use only actions, action types, or a list of them."); }); return false;