OptimisticUpdate<St> mixin
The OptimisticUpdate mixin is still EXPERIMENTAL. You can use it, but test it well.
Let's use a "Todo" app as an example. We want to save a new Todo to a TodoList.
This code saves the Todo, then reloads the TotoList from the cloud:
class SaveTodo extends ReduxAction<AppState> {
final Todo newTodo;
SaveTodo(this.newTodo);
Future<AppState> reduce() async {
try {
// Saves the new Todo to the cloud.
await saveTodo(newTodo);
}
finally {
// Loads the complete TodoList from the cloud.
var reloadedTodoList = await loadTodoList();
return state.copy(todoList: reloadedTodoList);
}
}
}
The problem with the above code is that it make take a second to update the todoList in the screen, while we save then load, which is not a good user experience.
The solution is optimistically updating the TodoList before saving the new Todo to the cloud:
class SaveTodo extends ReduxAction<AppState> {
final Todo newTodo;
SaveTodo(this.newTodo);
Future<AppState> reduce() async {
// Updates the TodoList optimistically.
dispatch(UpdateStateAction((state) => state.copy(todoList: state.todoList.add(newTodo))));
try {
// Saves the new Todo to the cloud.
await saveTodo(newTodo);
}
finally {
// Loads the complete TodoList from the cloud.
var reloadedTodoList = await loadTodoList();
return state.copy(todoList: reloadedTodoList);
}
}
}
That's better. But if the saving fails, the users still have to wait for the reload until they see the reverted state. We can further improve this:
class SaveTodo extends ReduxAction<AppState> {
final Todo newTodo;
SaveTodo(this.newTodo);
Future<AppState> reduce() async {
// Updates the TodoList optimistically.
var newTodoList = state.todoList.add(newTodo);
dispatch(UpdateStateAction((state) => state.copy(todoList: newTodoList)));
try {
// Saves the new Todo to the cloud.
await saveTodo(newTodo);
}
catch (e) {
// If the state still contains our optimistic update, we rollback.
// If the state now contains something else, we DO NOT rollback.
if (state.todoList == newTodoList) {
return state.copy(todoList: initialState.todoList); // Rollback.
}
}
finally {
// Loads the complete TodoList from the cloud.
var reloadedTodoList = await loadTodoList();
dispatch(UpdateStateAction((state) => state.copy(todoList: reloadedTodoList)));
}
}
}
Now the user sees the rollback immediately after the saving fails.
Note: If you are using a realtime database or Websockets to receive real-time updates from the
server, you may not need the finally block above, as long as the newTodoList
above can be
told apart from the current state.todoList
. This can be a problem if the state in question
is a primitive (boolean, number etc) or string.
The OptimisticUpdate mixin helps you implement the above code for you, when you provide the following:
- newValue: Is the new value, that you want to see saved and applied to the sate.
- getValueFromState: Is a function that extract the value from the given state.
- reloadValue: Is a function that reloads the value from the cloud.
- applyState: Is a function that applies the given value to the given state.
- Superclass Constraints
- ReduxAction<
St>
- ReduxAction<
Properties
-
dispatch
→ Dispatch<
St> -
Dispatches the action, applying its reducer, and possibly changing the store state.
The action may be sync or async.
no setterinherited
-
dispatchAll
→ List<
ReduxAction< Function(List<St> >ReduxAction< actions, {bool notify})St> > -
Dispatches all given
actions
in parallel, applying their reducer, and possibly changing the store state. It returns the same list ofactions
, so that you can instantiate them inline, but still get a list of them.no setterinherited -
dispatchAndWait
→ DispatchAndWait<
St> -
Dispatches the action, applying its reducer, and possibly changing the store state.
The action may be sync or async. In both cases, it returns a Future that resolves when
the action finishes.
no setterinherited
-
dispatchAndWaitAll
→ Future<
List< Function(List<ReduxAction< >St> >ReduxAction< actions, {bool notify})St> > -
Dispatches all given
actions
in parallel, applying their reducers, and possibly changing the store state. The actions may be sync or async. It returns a Future that resolves when ALL actions finish.no setterinherited -
dispatchAsync
→ DispatchAsync<
St> -
no setterinherited
-
dispatchSync
→ DispatchSync<
St> -
Dispatches the action, applying its reducer, and possibly changing the store state.
However, if the action is ASYNC, it will throw a StoreException.
no setterinherited
- env → Object?
-
Gets the store environment.
This can be used to create a global value, but scoped to the store.
For example, you could have a service locator, here, or a configuration value.
no setterinherited
- hashCode → int
-
The hash code for this object.
no setterinherited
- initialState → St
-
Returns the state as it was when the action was dispatched.
no setterinherited
- isFinished → bool
-
Returns true only if the action finished with no errors.
In other words, if the methods before, reduce and after all finished executing
without throwing any errors.
no setterinherited
- microtask → Future
-
To wait for the next microtask:
await microtask;
no setterinherited - runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- state → St
-
no setterinherited
- stateTimestamp → DateTime
-
no setterinherited
- status → ActionStatus
-
no setterinherited
-
store
→ Store<
St> -
no setterinherited
Methods
-
abortDispatch(
) → bool -
If abortDispatch returns true, the action will NOT be dispatched:
before
,reduce
andafter
will not be called, and the action will not be visible to the store observers.inherited -
after(
) → void -
This is an optional method that may be overridden to run during action
dispatching, after
reduce
. If this method throws an error, the error will be swallowed (will not throw). So you should only run code that can't throw errors. It may be synchronous only. Note this method will always be called, even if errors were thrown bybefore
orreduce
.inherited -
applyState(
Object? value, St state) → St -
Using the given
state
, you should apply the givenvalue
to it, and return the result. -
assertUncompletedFuture(
) → void -
An async reducer (one that returns Future<AppState?>) must never complete without at least
one await, because this may result in state changes being lost. It's up to you to make sure
all code paths in the reducer pass through at least one
await
.inherited -
before(
) → FutureOr< void> -
This is an optional method that may be overridden to run during action
dispatching, before
reduce
. If this method throws an error, thereduce
method will NOT run, but the methodafter
will. It may be synchronous (returningvoid
) ou async (returningFuture<void>
). You should NOT returnFutureOr
.inherited -
clearExceptionFor(
Object actionTypeOrList) → void -
Removes the given
actionTypeOrList
from the list of action types that failed.inherited -
exceptionFor(
Object actionTypeOrList) → UserException? -
Returns the UserException of the
actionTypeOrList
that failed.inherited -
getValueFromState(
St state) → Object? -
Using the given
state
, you should return thevalue
from that state. -
isFailed(
Object actionOrTypeOrList) → bool -
Returns true if an
actionOrActionTypeOrList
failed with an UserException. Note: This method uses the EXACT type inactionOrActionTypeOrList
. Subtypes are not considered.inherited -
isSync(
) → bool -
Returns true if the action is SYNC, and false if the action is ASYNC.
The action is considered SYNC if the
before
method, thereduce
method, and thewrapReduce
methods are all synchronous.inherited -
isWaiting(
Object actionOrTypeOrList) → bool -
You can use isWaiting to check if:
inherited
-
newValue(
) → Object? - You should return here the value that you want to update. For example, if you want to add a new Todo to the todoList, you should return the new todoList with the new Todo added.
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
prop<
V> (Object? key) → V -
Gets a property from the store.
This can be used to save global values, but scoped to the store.
For example, you could save timers, streams or futures used by actions.
inherited
-
reduce(
) → Future< St?> -
The
reduce
method is the action reducer. It may read the action state, the store state, and then return a new state (ornull
if no state change is necessary).override -
reloadValue(
) → Future< Object?> -
You should reload the
value
from the cloud. If you want to skip this step, simply don't provide this method. -
runtimeTypeString(
) → String -
Returns the runtimeType, without the generic part.
inherited
-
saveValue(
Object? newValue) → Future< void> -
You should save the
value
or other related value in the cloud. -
setProp(
Object? key, Object? value) → void -
Sets a property in the store.
This can be used to save global values, but scoped to the store.
For example, you could save timers, streams or futures used by actions.
inherited
-
setStore(
Store< St> store) → void -
inherited
-
toString(
) → String -
A string representation of this object.
inherited
-
waitAllActions(
List< ReduxAction< actions, {bool completeImmediately = false}) → Future<St> >void> -
Returns a future that completes when ALL given
actions
finished dispatching. You MUST provide at list one action, or an error will be thrown.inherited -
waitCondition(
bool condition(St), {int? timeoutMillis}) → Future< ReduxAction< St> ?> -
Returns a future which will complete when the given state
condition
is true. If the condition is already true when the method is called, the future completes immediately.inherited -
wrapError(
Object error, StackTrace stackTrace) → Object? -
If any error is thrown by
reduce
orbefore
, you have the chance to further process it by usingwrapError
. Usually this is used to wrap the error inside of another that better describes the failed action. For example, if some action converts a String into a number, then instead of throwing a FormatException you could do:inherited -
wrapReduce(
Reducer< St> reduce) → Reducer<St> -
You may wrap the reducer to allow for some pre- or post-processing.
For example, if you want to prevent an async reducer to change the current state,
if the current state has already changed since when the reducer started:
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited