Skip to content

Commit

Permalink
Improved isWaiting tests and example.
Browse files Browse the repository at this point in the history
  • Loading branch information
marcglasberg committed May 20, 2024
1 parent c61d42c commit 381c943
Show file tree
Hide file tree
Showing 5 changed files with 407 additions and 19 deletions.
135 changes: 135 additions & 0 deletions example/lib/main_spinner_with_store_connector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Developed by Marcelo Glasberg (2019) https://glasberg.dev and https://github.com/marcglasberg
// For more info, see: https://pub.dartlang.org/packages/async_redux
import 'dart:async';
import 'package:async_redux/async_redux.dart';
import 'package:flutter/material.dart';

late Store<AppState> store;

/// This example shows a counter and a button.
/// When the button is tapped, the counter will increment synchronously.
void main() {
store = Store<AppState>(initialState: AppState(counter: 0, something: 0));
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => StoreProvider<AppState>(
store: store,
child: const MaterialApp(home: HomePage()),
);
}

class HomePage extends StatelessWidget {
const HomePage({
super.key,
});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Spinner With StoreConnector')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
CounterWidget(),
],
),
),
// Here we disable the button while the `WaitAndIncrementAction` action is running.
floatingActionButton: _PlusButtonConnector(),
);
}
}

class _PlusButtonConnector extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, ViewModel>(
vm: () => Factory(this),
builder: (context, vm) {
return vm.isWaiting
? const FloatingActionButton(
disabledElevation: 0,
onPressed: null,
child: SizedBox(width: 25, height: 25, child: CircularProgressIndicator()))
: FloatingActionButton(
disabledElevation: 0,
onPressed: () => context.dispatch(WaitAndIncrementAction()),
child: const Icon(Icons.add),
);
},
);
}
}

class Factory extends VmFactory<AppState, _PlusButtonConnector, ViewModel> {
Factory(connector) : super(connector);

@override
ViewModel fromStore() {
return ViewModel(
isWaiting: isWaiting(WaitAndIncrementAction),
);
}
}

class ViewModel extends Vm {
final bool isWaiting;

ViewModel({
required this.isWaiting,
}) : super(equals: [isWaiting]);
}

/// This action waits for 2 seconds, then increments the counter by [amount]].
class WaitAndIncrementAction extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
await Future.delayed(const Duration(seconds: 2));
return AppState(
counter: state.counter + 1,
something: state.something,
);
}
}

class CounterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
var isWaiting = context.isWaiting(WaitAndIncrementAction);

return Text(
'${context.state.counter}',
style: TextStyle(fontSize: 40, color: isWaiting ? Colors.grey[350] : Colors.black),
);
}
}

extension _BuildContextExtension on BuildContext {
AppState get state => getState<AppState>();
}

class AppState {
int counter;
int something;

AppState({
required this.counter,
required this.something,
});

@override
String toString() => 'AppState{counter: $counter}';

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is AppState && runtimeType == other.runtimeType && counter == other.counter;

@override
int get hashCode => counter.hashCode;
}
4 changes: 2 additions & 2 deletions lib/src/action_mixins.dart
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ mixin OptimisticUpdate<St> on ReduxAction<St> {
Future<St?> reduce() async {
// Updates the value optimistically.
final _newValue = newValue();
final action = UpdateStateAction((St state) => applyState(_newValue, state));
final action = UpdateStateAction.withReducer((St state) => applyState(_newValue, state));
dispatch(action);

try {
Expand All @@ -454,7 +454,7 @@ mixin OptimisticUpdate<St> on ReduxAction<St> {
} finally {
try {
final Object? reloadedValue = await reloadValue();
final action = UpdateStateAction((St state) => applyState(reloadedValue, state));
final action = UpdateStateAction.withReducer((St state) => applyState(reloadedValue, state));
dispatch(action);
} on UnimplementedError catch (_) {
// If the reload was not implemented, do nothing.
Expand Down
25 changes: 21 additions & 4 deletions lib/src/redux_action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -489,13 +489,30 @@ class AbortDispatchException implements Exception {
}

/// The [UpdateStateAction] action is used to update the state of the Redux store, by applying
/// the given [updateFunction] to the current state.
/// the given [reducerFunction] to the current state.
class UpdateStateAction<St> extends ReduxAction<St> {
//
final St? Function(St) updateFunction;
final St? Function(St) reducerFunction;

UpdateStateAction(this.updateFunction);
/// When you don't need to use the current state to create the new state, you
/// can use the `UpdateStateAction` factory.
///
/// Example:
/// ```
/// var newState = AppState(...);
/// store.dispatch(UpdateStateAction(newState));
/// ```
factory UpdateStateAction(St state) => UpdateStateAction.withReducer((_) => state);

/// When you need to use the current state to create the new state, you
/// can use `UpdateStateAction.withReducer`.
///
/// Example:
/// ```
/// store.dispatch(UpdateStateAction.withReducer((state) => state.copy(...)));
/// ```
UpdateStateAction.withReducer(this.reducerFunction);

@override
St? reduce() => updateFunction(state);
St? reduce() => reducerFunction(state);
}
11 changes: 9 additions & 2 deletions lib/src/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1700,7 +1700,7 @@ class Store<St> {
}
//
finally {
_finalize(action, originalError, processedError, afterWasRun);
_finalize(action, originalError, processedError, afterWasRun, notify);
}

return action._status;
Expand Down Expand Up @@ -1750,7 +1750,7 @@ class Store<St> {
}
//
finally {
_finalize(action, originalError, processedError, afterWasRun);
_finalize(action, originalError, processedError, afterWasRun, notify);
}

return action._status;
Expand Down Expand Up @@ -2041,12 +2041,19 @@ class Store<St> {
Object? error,
Object? processedError,
_Flag<bool> afterWasRun,
bool notify,
) {
if (!afterWasRun.value) _after(action);

bool ifWasRemoved = _actionsInProgress.remove(action);
if (ifWasRemoved) _checkAllActionConditions(action);

// If we'll not be notifying, it's possible we need to trigger the change controller, when the
// action is awaitable (that is to say, when we have already called `isWaiting` for this action).
if (!notify && _awaitableActions.contains(action.runtimeType)) {
_changeController.add(state);
}

createTestInfoSnapshot(state!, action, error, processedError, ini: false);

if (_actionObservers != null)
Expand Down
Loading

0 comments on commit 381c943

Please sign in to comment.