# command_it **Repository Path**: yeild_libs/command_it ## Basic Information - **Project Name**: command_it - **Description**: No description available - **Primary Language**: Dart - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-02 - **Last Updated**: 2025-08-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # command_it ![Build](https://github.com/escamoteur/command_it/workflows/Build/badge.svg) [![codecov](https://codecov.io/gh/escamoteur/command_it/branch/master/graph/badge.svg)](https://codecov.io/gh/escamoteur/command_it) command_it is a way to manage your state based on `ValueListenable` and the `Command` design pattern. Sounds scary uh? Ok lets try it a different way. A `Command` is an object that wraps a function that can be executed by calling the command, therefore decoupling your UI from the wrapped function. It's not that easy to define what exactly state management is (see https://medium.com/super-declarative/understanding-state-management-and-why-you-never-will-dd84b624d0e ). For me it's how the UI triggers processes in the model/business layer of your app and how to get back the results of these processes to display them. For both aspects `command_it` offers solution plus some nice extras. So in a way it offers the same that BLoC does but in a more logical way. >This readme might seem very long, but it will guide you easily step by step through all features of `command_it`. > **Breaking Change** A `Command` by default will always notify changes unlike, `ValueNotifier` which only notifies its liteners when the value it holds changes. For more details check this [section](#a-command-always-notifies-by-default). ## Why Commands When I started Flutter the most often recommended way to manage your state was `BLoC`. What never appealed to me was that in order to execute a process in your model layer you had to push an object into a `StreamController` which just didn't feel right. For me triggering a process should feel like calling a function. Coming from the .Net world I was used to use Commands for this, which had an additional nice feature that the Button that triggered the command would automatically disable for the duration, the command was running and by this, preventing a double execution at the same time. I also learned to love a special breed of .Net commands called `ReactiveCommands` which emitted the result of the called function on their own Stream interface (the ReactiveUI community might oversee that I don't talk of Observables here.) As I wanted to have something similar I ported `ReactiveCommands` to Dart with my [rx_command](https://pub.dev/packages/rx_command). But somehow they did not get much attention because 1. I didn't call them state management and 2. they had to do with `Streams` and even had that scary `rx` in the name and probably the readme wasn't as good to start as I thought. Remi Rousselet talked to me about that `ValueNotifier` and how much easier they are than using Streams. So what you have here is my second attempt to warm the hearts of the Flutter community for the `Command` metaphor absolutely free of `Streams` ## A first careful en*counter* Let's start with the (in)famous counter example but by using a `Command`. As said before a `Command` wraps a function and can publish the result in a way that can be consumed by the UI. It does this by implementing the `ValueListenable` interface which means a command behaves like `ValueNotifier`. A command has the type: ```Dart Command ``` at which `TParam` is the type of the parameter which the wrapped function expects as argument and `TResult` is the type of the result of it, which means the `Command` behaves like a `ValueNotifier`. ### A Command always notifies (by default) A `Command` by default will always notify changes unlike, `ValueNotifier` which only notifies its listeners when the value it holds changes. This behavior is opted to enable the widgets to always rebuild whenever a command is executed. If you prefer the Command to behave exactly like a `ValueNotifier` then the default behaviour can be turned of by setting the `notifyOnlyWhenValueChanges` parameter to `true`. In the included project `counter_example` the command is defined as: ```Dart class _MyHomePageState extends State { int counter = 0; /// This command does not expect any parameters when called therefore TParam /// is void and publishes its results as String Command _incrementCounterCommand; _MyHomePageState() { _incrementCounterCommand = Command.createSyncNoParam(() { counter++; return counter.toString(); }, '0'); } ``` To create a `Command` the `Command` class [offers](#how-to-create-commands) different static functions depending on the signature of the wrapped function. In this case we want to use a synchronous function without any parameters. Our widget tree now looks like this: ```Dart body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), ValueListenableBuilder( valueListenable: _incrementCounterCommand, builder: (context, val, _) { return Text( val, style: Theme.of(context).textTheme.headline4, ); }), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounterCommand, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); ``` As `Command` is a [callable class](https://dart.dev/guides/language/language-tour#callable-classes), so we can pass it directly to the `onPressed` handler of the `FloatingActionButton` and it will execute the wrapped function. The result of the function will get assigned to the `Command.value` so that the `ValueListenableBuilder` updates automatically. **This is a very basic demo! In a real all you wouldn't place a command in a Widgets State** ## Commands in full power mode So far the command did not do more than what you could do with BLoC, besides that you could call it like a function and didn't need a Stream. But `Command` can do more than that. It allows us to: * Update the UI based on if the `Command` is executing * React on Exceptions in the wrapped functions * Control when a `Command` can be executed Let's explore this features by examining the included `example` app which queries an open weather service and displays a list of cities with the current weather. ![](https://github.com/escamoteur/command_it/blob/master/misc/screen_shot_example.png) The app uses a `WeatherManager` which contains the `Command` to update the `ListView` by making a REST call: ```Dart Command> updateWeatherCommand; ``` The `updateWeatherCommand` expects a search term and will return a list of `WeatherEntry`. The `Command` gets initialized in the constructor of the `WeatherManager`: ```Dart updateWeatherCommand = Command.createAsync>( update, // Wrapped function [], // Initial value restriction: setExecutionStateCommand, //please ignore for the moment ) ``` `update` is the asynchronous function that queries the weather service, therefore we create an async version of `Command` using the `createAsync` constructor. ### Updating the ListView In `listview.dart`: ```Dart class WeatherListView extends StatelessWidget { WeatherListView(); @override Widget build(BuildContext context) { return ValueListenableBuilder>( valueListenable: weatherManager.updateWeatherCommand, builder: (BuildContext context, List data, _) { // only if we get data return ListView.builder( itemCount: data.length, .... ``` ### Reacting on changes of the function execution state `Command` has a property ```Dart ValueListenable isExecuting; ``` that has the value of `false` while the wrapped function isn't executed and `true` when it is. So we use this in the UI in `homepage.dart` to display a progress indicator while the app waits for the result of the REST call: ```Dart child: ValueListenableBuilder( valueListenable: weatherManager.updateWeatherCommand.isExecuting, builder: (BuildContext context, bool isRunning, _) { // if true we show a buys Spinner otherwise the ListView if (isRunning == true) { return Center( child: SizedBox( width: 50.0, height: 50.0, child: CircularProgressIndicator(), ), ); } else { return WeatherListView(); } }, ), ``` > :triangular_flag_on_post: As it's not possible to update the UI while a synchronous function is being executed `Commands` that wrap a synchronous function don't support `isExecuting` and will throw an assertion if you try to access it. ### Update the UI on change of the search field As we don't want to send a new HTTP request on every keypress in the search field we don't directly wire the `onChanged` event to the `updateWeatherCommand`. Instead we use a second `Command` to convert the `onChanged` event to a `ValueListenable` so that we can use the `debounce` and `listen` function of my extension function package `listen_it`: For this a synchronous `Command` is sufficient: ```Dart // in weather_viewmodel.dart: Command textChangedCommand; // and in the constructor: // Will be called on every change of the searchfield textChangedCommand = Command.createSync((s) => s, ''); // // make sure we start processing only if the user make a short pause typing textChangedCommand.debounce(Duration(milliseconds: 500)).listen( (filterText, _) { // I could omit the execute because Command is a callable // class but here it makes the intention clearer updateWeatherCommand.execute(filterText); }, ); ``` In the `homepage.dart`: ```Dart child: TextField( /// I omitted some properties from the example here onChanged: weatherManager.textChangedCommand, ), ``` ### Restricting command execution Sometimes it is desirable to make the execution of a `Command` depending on some other state. For this you can pass a `ValueListenable` as `restriction` parameter, when you create a command. If you do so the command will only be executed if the value of the passed listenable is `false`. In the example app we can restrict the execution by changing the state of a `Switch`. To handle changes of the `Switch` we use..., you guessed it, another command in the `WeatherManager`: ```Dart WeatherManager() { // Command expects a bool value when executed and sets it as its own value setExecutionStateCommand = Command.createSync((b) => b, true); // We pass the result of switchChangedCommand as restriction to the updateWeatherCommand updateWeatherCommand = Command.createAsync>( update, // Wrapped function [], // Initial value /// as the switch is on when the command can be executed we need to invert the value /// to make the command disabled when the switch is off restriction: setExecutionStateCommand.map((switchState) => !switchState), ); ... ``` To update the `Switch` we use again a `ValueListenableBuilder`: ```Dart ValueListenableBuilder( valueListenable: weatherManager.setExecutionStateCommand, builder: (context, value, _) { return Switch( value: value, onChanged: weatherManager.setExecutionStateCommand, ); }) ``` ### Disabling the update button while another update is in progress The update button should not be active while an update is running or when the `Switch` deactivates it. We could achieve this, again by using the `isExecuting` property of `Command` but we would have to somehow combine it with the value of `setExecutionStateCommand` which is cumbersome. Luckily `Command` has another property `canExecute` which reflects a combined value of `!isExecuting && restriction`. So we can easily solve this requirement with another....wait for it...`ValueListenableBuilder` ```Dart child: ValueListenableBuilder( valueListenable: weatherManager .updateWeatherCommand .canExecute, builder: (BuildContext context, bool canExecute, _) { // Depending on the value of canExecute we set or clear the handler final handler = canExecute ? weatherManager.updateWeatherCommand : null; return RaisedButton( child: Text("Update"), color: Color.fromARGB(255, 33, 150, 243), textColor: Color.fromARGB(255, 255, 255, 255), onPressed: handler, ); }, ), ``` ### Error Handling If the wrapped function inside a `Command` throws an `Exception` the `Command` catches it so your App won't crash. Instead it will wrap the caught error together with the value that was passed when the command was executed in a `CommandError` object and assign it to the `Command's` `thrownExeceptions` property which is a `ValueListenable`. So to react on occurring error you can register your handler with `addListener` or use my `listen` extension function from `listen_it` as it is done in the example: ```Dart /// in HomePage.dart @override void didChangeDependencies() { errorSubscription ??= weatherManager .updateWeatherCommand .errors .where((x) => x != null) // filter out the error value reset .listen((error, _) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('An error has occured!'), content: Text(error.toString()), )); }); super.didChangeDependencies(); } ``` Unfortunately its not possible to reset the value of a `ValueNotifier` without triggering its listeners. So if you have registered a listener you will get it called at every start of a `Command` execution with a value of `null` and clear all previous errors. If you use `listen_it` you can do it easily by using the `where` extension. ### Error handling the fine print You can tweak the behaviour of the error handling by passing a `catchAlways` parameter to the factory functions. If you pass `false` Exceptions will only be caught if there is a listener on `errors` or on `results` (see next chapter). You can also change the default behaviour of all `Command` in your app by changing the value of the `catchAlwaysDefault` property. During development its a good idea to set it to `false` to find any non handled exception. In production, setting it to `true` might be the better decision to prevent hard crashes. Note that `catchAlwaysDefault` property will be implicitly ignored if the `catchAlways` parameter for a command is set. `Command` also offers a static global Exception handler: ```Dart static void Function(String commandName, CommandError error) globalExceptionHandler; ``` If you assign a handler function to it, it will be called for all Exceptions thrown by any `Command` in your app independent of the value of `catchAlways` if the `Command` has no listeners on `errors` or on `results`. The overall work flow of exception handling in command_it is depicted in the following diagram. ![](https://github.com/escamoteur/command_it/blob/master/misc/exception_handling.png) ## Getting all data at once `isExecuting` and `errors` are great properties but what if you don't want to use separate `ValueListenableBuilders` for each of them plus one for the data? `Command` got you covered with the `results` property that is an `ValueListenable` which combines all needed data and is updated several times during a `Command` execution. ```Dart /// Combined execution state of an `Command` /// Will be updated for any state change of any of the fields /// 1. If the command was just newly created `results.value` has the value: /// `param data,null, null, false` (paramData,data, error, isExecuting) /// 2. When calling execute: `param data, null, null, true` /// 3. When execution finishes: `param data, the result, null, false` /// If an error occurs: `param data, null, error, false` /// `param data` is the data that you pass as parameter when calling the command class CommandResult { final TParam paramData; final TResult data; final Object error; final bool isExecuting; bool get isSuccsess => !hasError && !isExecuting; bool get hasData => data != null; bool get hasError => error != null; /// This is a stripped down version of the class. Please see the source } ``` You can find a Version of the Weather app that uses this approach in `example_command_results`. There the `homepage.dart` looks like: ```Dart child: ValueListenableBuilder< CommandResult>>( valueListenable: weatherManager.updateWeatherCommand.results, builder: (BuildContext context, result, _) { if (result.isExecuting) { return Center( child: SizedBox( width: 50.0, height: 50.0, child: CircularProgressIndicator(), ), ); } else if (result.hasData) { return WeatherListView(result.data); } else { assert(result.hasError); return Column( children: [ Text('An Error has occurred!'), Text(result.error.toString()), if (result.error != null) Text('For search term: ${result.paramData}') ], ); } }, ), ``` Even if you use `results` the other properties are updated as before, so you can mix both approaches as you need it. For instance use `results` as above but additionally listening to `errors` for logging. If you want to be able to always display data (while loading or in case of an error) you can pass `includeLastResultInCommandResults=true`, the last successful result will be included as `data` unless a new result is available. ### CommandBuilder, reducing boilerplate `command_it` includes a `CommandBuilder` widget which makes the code above a bit nicer: ```Dart child: CommandBuilder>( command: weatherManager.updateWeatherCommand, whileExecuting: (context, _) => Center( child: SizedBox( width: 50.0, height: 50.0, child: CircularProgressIndicator(), ), ), onData: (context, data, _) => WeatherListView(data), onError: (context, error, param) => Column( children: [ Text('An Error has occurred!'), Text(error.toString()), if (error != null) Text('For search term: $param') ], ), ), ``` In case your Command does not return a value you can use the `onSuccess` builder. ### toWidget() extension method on Command Result I you are using a package `get_it_mixin`, `provider` or `flutter_hooks` you probably don't want to use the `CommandBuilder` for you there is an extension method for the `CommandResult` type that you can use like this: ```Dart return result.toWidget( whileExecuting: (lastValue, _) => Center( child: SizedBox( width: 50.0, height: 50.0, child: CircularProgressIndicator(), ), ), onResult: (data, _) => WeatherListView(data), onError: (error, lastValue, paramData) => Column( children: [ Text('An Error has occurred!'), Text(result.error.toString()), if (result.error != null) Text('For search term: ${result.paramData}') ], ), ); ``` ## How to create Commands ´Command´ offers different static factory functions for the different function types you want to wrap: ```Dart /// for syncronous functions with no parameter and no result static Command createSyncNoParamNoResult( void Function() action, { ValueListenable? restriction, void Function()? ifRestrictedExecuteInstead, bool? catchAlways, bool notifyOnlyWhenValueChanges = false, String? debugName, }) /// for syncronous functions with one parameter and no result static Command createSyncNoResult( void Function(TParam x) action, { ValueListenable? restriction, ExecuteInsteadHandler? ifRestrictedExecuteInstead, bool? catchAlways, bool notifyOnlyWhenValueChanges = false, String? debugName, }) /// for syncronous functions with no parameter and but a result static Command createSyncNoParam( TResult Function() func, TResult initialValue, { ValueListenable? restriction, void Function()? ifRestrictedExecuteInstead, bool includeLastResultInCommandResults = false, bool? catchAlways, bool notifyOnlyWhenValueChanges = false, String? debugName, }) /// for syncronous functions with one parameter and result static Command createSync( TResult Function(TParam x) func, TResult initialValue, { ValueListenable? restriction, ExecuteInsteadHandler? ifRestrictedExecuteInstead, bool includeLastResultInCommandResults = false, bool? catchAlways, bool notifyOnlyWhenValueChanges = false, String? debugName, }) /// and for Async functions: static Command createAsyncNoParamNoResult( Future Function() action, { ValueListenable? restriction, void Function()? ifRestrictedExecuteInstead, bool? catchAlways, bool notifyOnlyWhenValueChanges = false, String? debugName, }) static Command createAsyncNoResult( Future Function(TParam x) action, { ValueListenable? restriction, ExecuteInsteadHandler? ifRestrictedExecuteInstead, bool? catchAlways, bool notifyOnlyWhenValueChanges = false, String? debugName, }) static Command createAsyncNoParam( Future Function() func, TResult initialValue, { ValueListenable? restriction, void Function()? ifRestrictedExecuteInstead, bool includeLastResultInCommandResults = false, bool? catchAlways, bool notifyOnlyWhenValueChanges = false, String? debugName, }) static Command createAsync( Future Function(TParam x) func, TResult initialValue, { ValueListenable? restriction, ExecuteInsteadHandler? ifRestrictedExecuteInstead, bool includeLastResultInCommandResults = false, bool? catchAlways, bool notifyOnlyWhenValueChanges = false, String? debugName, }) ``` For detailed information on the parameters of these functions consult the API docs or the source code documentation. ## Reacting on Functions with no results Even if your wrapped function doesn't return a value, you can react on the end of the function execution by registering a listener to the `Command`. The command Value will be void but your handler is ensured to be called. ## Restricting Commands in Detail As described above you can pass in a `ValueListenable` named `restriction` this allows to control the executability of a Command from the outside. Typical example would be if a user is logged in. To allow you to declarative describe what should happen if the user tries to executed a restricted Command you can pass in an optional `ifRestrictedExecuteInstead` handler function that get the parameter of the command passed in if the command expects a parameter. This can be nicely used to push a login screen in the case described above. ## Logging If you are not sure what's going on in your App you can register an handler function to ```Dart static void Function(String commandName, CommandResult result) loggingHandler; ``` It will get executed on every `Command` execution in your App. `commandName` is the optional `debugName` that you can pass when creating a command. ## Awaiting Commands In general you shouldn't await a command as it goes against the reactive philosophy. Your UI should react to the result of the command by "listening" to one of its `ValueListenable` interfaces. In case you really need to await the completion of a command you can use the `executeWithFuture()` function of the Command. `executeWithFuture` starts the execution of the Command and returns a `Future` that completes when the function that it wraps. The main reason that this function exists is that you can use `RefreshIndicator` directly with a command like: ```Dart return RefreshIndicator( onRefresh: () => updateMovieCmd.executeWithFuture(), child: GridView.extent( maxCrossAxisExtent: 200, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 0.7, children: movies.data.map((movie) => _MovieBox(movie: movie)).toList(), ), ); ``` ## Commands and the get_it_mixin If you want to use Commands as comfortable as possible, check out the [get_it_mixin](https://pub.dev/packages/get_it_mixin) with its `watchX` function. With it you can use Commands without any Builders in a very intuitive way.