A simple use case that executes a specific task which returns a Left Value when an error occurs or a Right Value when the task succeeds.
This is ideal for create, read, update and delete (CRUD) operations.
A Runner use case can emit 4 possible class states, all of which inherit from the RunnerState sealed class:
Runner States
Description
RunnerInitial
The initial state or the state emitted when the use case has been reset.
Running
The state emitted when the task execution is in progress.
RunSuccess
The state emitted when the use case fails.
RunFailed
The state emitted when the use case succeeds.
A Runner also has the following properties that you can access:
Property
Description
value
The latest value returned when calling run(). This may either be the leftValue if RunFailed was recently emitted. Otherwise, this will be equal to the rightValue if RunSuccess was more recent.
params
The latest params passed when calling run(). This may either be the leftParams if RunFailed was recently emitted. Otherwise, this will be equal to the rightParams if RunSuccess was more recent.
leftValue
The last left value returned when a failed run() was called.
leftParams
The last left params passed when a failed run() was called.
rightValue
The last right value returned when a successful run() was called.
rightParams
The last right params passed when a successful run() was called.
Creating a Runner
Let's create a Runner use case named CountFruits for categorizing and counting the number of fruits given in our fruit basket.
Create the following classes:
1. Create the Parameter class
This is the parameter class passed to the Runner when being executed.
/// The parameter for the [CountFruit] use case containing all the available/// fruits to count.classCountFruitParams {constCountFruitParams(this.fruits);/// The fruits in our fruit basket. /// /// Example: /// [ apple, orange, mango, apple, apple, mango ]finalList<String> fruits;@overrideStringtoString() => {'fruits': fruits}.toString();}
2. Create the Left value class
This is the object returned when the Runner fails and emits a RunFailed state:
This is the object returned when the Runner succeeds and emits a RunSuccess state:
/// The right value for [CountFruit] containing the count for each fruit.classCountFruitResult {constCountFruitResult(this.fruitCount);/// The fruits counted by type /// /// Example: /// { apple: 3, orange: 1, mango: 2 }finalMap<String, int> fruitCount;@overrideStringtoString() =>'$fruitCount';}
4. Create the Runner
Pass the Parameter, Left and Right classes to the Runner's generic arguments. Afterwards, implement the code logic in the onCall method:
/// A runner that counts the quantity of each given fruit.classCountFruitextendsRunner<CountFruitParams, Failure, CountFruitResult> {/// A callback function triggered when the [run] method is called.@overrideFutureOr<Either<Failure, CountFruitResult>> onCall(CountFruitParams params, ) async {if (params.fruits.isEmpty) {// When the given fruits is empty, then a left value is returnedreturnconstLeft(Failure('There are no fruits to count')); }final fruitCount =<String, int>{};for (final fruit in params.fruits) { fruitCount[fruit] = (fruitCount[fruit] ??0) +1; }// Returns a right value containing the fruit countfinal result =CountFruitResult(fruitCount);returnRight(result); }}
Using a Runner
1. Start the Runner
To start the runner, call the run method:
final countFruit =CountFruit();await countFruit.run( params:constCountFruitParams(['Apple', 'Orange', 'Apple']), );
The Runner will emit the Running state, followed either by the RunSuccess with a Right Value or RunFailed state with a Left Value depending on the result of the runner.
2. View the Runner properties
The Runner gives you access to the following properties:
// The recent value returned when calling `run()`. This may either be the // `leftValue` if the state current is `RunFailed` or the `rightValue` if the // current state is `RunSuccess`print('Current value: ${runner.value}');// The recent params passed when calling `run()`. This may either be the// `leftParams` if the state current is `RunFailed` or the `rightParams` if // the current state is `RunSuccess`print('Current params: ${runner.params}');// The last left value returned when a failed `run()` was calledprint('Last left value: ${runner.leftValue}');// The last left params passed when a failed `run()` was calledprint('Last left params: ${runner.leftParams}');// The last right value returned when a successful `run()` was calledprint('Last right value: ${runner.rightValue}');// The last right params passed when a successful `run()` was calledprint('Last right params: ${runner.rightParams}');
3.Reset the Runner
To clear the Runner and reset it back to its initial state, call the reset method:
runner.reset();
Flutter Example
Every use case is a descendent of BLoC cubit. Hence, we can manage its states via the flutter_bloc package.
BlocProvider( lazy:false, create: (context) =>// Initialize and eagerly run the RunnerCountFruit() ..run( params:constCountFruitParams(['apple', 'orange', 'mango', 'apple']), ), child:BlocConsumer<CountFruit, RunnerState>( listener: (context, runnerState) {if (runnerState isRunnerInitial) {// Handle initial state. This is also triggered when the Runner has// been reset } elseif (runnerState isRunning) {// Handle running state } elseif (runnerState isRunFailed<Failure>) {// Handle failureprint('Left value: ${runnerState.leftValue}'); } elseif (runnerState isRunSuccess<CountFruitResult>) {// Handle successprint('Right value: ${runnerState.rightValue}'); } }, builder: (context, runnerState) {switch (runnerState) {caseRunnerInitial():returnContainer(); caseRunning():returnconstCenter(child:CircularProgressIndicator());caseRunFailed():returnCenter( child:Text('Left value: ${runnerState.leftValue}'), );caseRunSuccess():returnCenter( child:Text('Right value: ${runnerState.rightValue}'), ); } }, ),);