Welcome to part 4 of my series on Flutter Architecture:
- Introduction
- Fundamentals of Dart Streams
- RxDart: Magical transformations of Streams
- RxVMS foundations: RxCommand and GetIt (this post)
- RxVMS: Services & Managers
- RxVMS: The self-responsible widget
- User Authentication the RxVMS way
While the last two posts weren't specifically aimed at RxVMS, they are a necessary requirement to understand the RxVMS architecture. Today, we turn to the important packages you will need to use RxVMS in your own App.
GetIt: The Fast ServiceLocator
When you recall the diagram picturing the different elements of an RxVMS app...
...you might have wondered how the various views, managers, and services are able to know of each other. More importantly, you may be wondering how one element is able to access another one's functions.
While there are many different ways to achieve this (e.g. Inherited Widgets, IoC containers, dependency injections...), I personally prefer the use of a service locator. I wrote a whole blog post on the use of my Service Locator GetIt, but I'll give a short introduction here as well. Basically, you register types or objects in the ServiceLocator (SL) once, and then you can access these objects from anywhere in your app. This is similar to how you'd use a Singleton...but with more flexibility.
Usage
GetIt's usage is pretty straightforward. At the start of your app, you register the Services or Managers that you will want to access later in other parts of your app. After that, you call the SL to access instances of the registered types.
The nice thing is that you can register an interface or abstract class together with a concrete implementation. When accessing the instance, you always ask for the interface/abstract class type. This makes it easy to switch the implementation by just switching the concrete type at registration time. This allows you to easily switch a real Service for a MockService.
Getting practical
I typically initialize my SL in a file called service_locator.dart
, which also contains the global (ambient) variable for the SL. This will be the only global variable in your app. (As GetIt is also a singleton itself you don't have to use a global variable. To keep the code concise when using GetIt I personally always use a global variable.)
// This is an example of a current project
GetIt sl = GetIt.instance;
void setUpServiceLocator(ErrorReporter reporter) {
// Services
// [registerSingleton] registers an instance of an object with the type or a derived-type
// of the one you pass as a generic Parameter
// sl.get<ErrorReporter>.get() will always return that instance
sl.registerSingleton<ErrorReporter>(reporter);
// [registerLazySingleton] gets passed a factory function that returns the type or a derived-type
// of the one you pass as a generic Parameter
// sl.get<ImageService>.get() will call the registered factory function on the first call and store
// the returned instance. On any subsequent calls, the stored instance is returned
sl.registerLazySingleton<ImageService>(() => ImageServiceImplementation());
sl.registerLazySingleton<MapService>(() => MapServiceImplementation());
// Managers
sl.registerSingleton<UserManager>(UserManagerImplementation());
sl.registerLazySingleton<EventManager>(() => EvenManagerImplementation());
sl.registerLazySingleton<ImageManager>(() => ImageManagerImplementation());
sl.registerLazySingleton<AppManager>(() => AppManagerImplementation());
}
Whenever you want to access one of these objects, simply call
RegistrationType object = sl.get<RegistrationType>();
//because GetIt is a callable class, you can write this even shorter:
RegistrationType object2 = sl<RegistrationType>();
If this was a bit too quick, please read my blog post for more details.
RxCommand
Now that we are using GetIt to access our Objects from anywhere (including the UI), I want to look at how we can implement handler functions for UI events. The simplest approach would be to add functions to our Manager objects and use them in our Widgets:
class SearchManager {
void lookUpZip(String zip);
}
Then in the UI:
TextField(onChanged: sl.get<SearchManager>().lookUpZip,)
This would call lookUpZip
on every change in the TextField. But how do we display the result? As we try to be reactive, we could add a StreamController to the SearchManager
abstract class SearchManager {
Stream<String> get nameOfCity;
void lookUpZip(String zip);
}
class SearchManagerImplementation implements SearchManager{
@override
Stream<String> get nameOfCity => cityController.stream;
StreamController<String> cityController = new StreamController();
@override
Future<void> lookUpZip(String zip) async
{
var cityName = await sl.get<ZipApiService>().lookUpZip(zip);
cityController.add(cityName);
}
}
Then in the UI we can use:
StreamBuilder<String>(
initialData:''
stream: sl.get<SearchManager>().nameOfCity,
builder: (context, snapshot) => Text(snapShot.data);
While this approach does work, it isn't optimal. Issues include:
- boilerplate - We must always create a function, a
StreamController
, and a getter for the Stream if we don't want to expose the wholeStreamController
. - busy states - While the function is running, what if we want our UI to show a Spinner?
- error handling - What happens if
lookUpZip
throws an exception?
Sure, we could add more StreamControllers to handle busy states and errors...but this gets tedious if we have to do that manually for every event handler. That's where the rx_command
package comes in handy.
RxCommand
solves all of the above issues and more. An RxCommand
encapsulates a function (sync or async) and automatically publishes its results on a Stream.
With RxCommand
, we could rewrite our manager to:
abstract class SearchManager {
RxCommand<String,String> lookUpZipCommand;
}
class SearchManagerImplementation implements SearchManager{
@override
RxCommand<String,String> lookUpZipCommand;
SearchManagerImplementation()
{
lookUpZipCommand = RxCommand.createAsync((zip) =>
sl.get<ZipApiService>().lookUpZip(zip));
}
}
In the UI:
TextField(onChanged: sl.get<SearchManager>().lookUpZipCommand,)
...
StreamBuilder<String>(
initialData:''
stream: sl.get<SearchManager>().lookUpZipCommand,
builder: (context, snapshot) => Text(snapShot.data);
Which is much more concise and readable.
RxCommand in Detail
RxCommand
has one input and five output Observables
:
- canExecuteInput is an optional
Observable<bool>
which you can pass to the factory function when creating anRxCommand
. It signals theRxCommand
if it can be executed, depending on the last value it received over this Observable. - isExecuting is an
Observable<bool>
that signals if the command is currently executing its wrapped function. When a command is busy, it cannot be triggered a second time. If you want to display a Spinner while the wrapped function is running, listen toisExecuting
. - canExecute is an
Observable<bool>
that signals if the command can be executed. This synergizes well with a StreamBuilder to change the appearance of a button between enabled/disabled.- Its value is actually:
Observable<bool> canExecute = Observable.combineLatest2<bool,bool>(canExecuteInput,isExecuting)
=> canExecuteInput && !isExecuting).distinct.
- Which means:
- It will emit
false
ifisExecuting
emitstrue
- It will emit
true
only ifisExecuting
emitsfalse
ANDcanExecuteInput
hasn't emittedfalse
.
- It will emit
- thrownExceptions is an
Observable<Exeption>
. All thrown exceptions that the wrapped function may throw will be caught and emitted on this Observable. It is ideal to listen to it and display a dialog if an error occurs. -
(the command itself) is actually an Observable. Values returned by the wrapped function will be emitted on that channel, so you can directly pass an
RxCommand
to aStreamBuilder
as astream
parameter. - results contain all command states in one
Observable<CommandResult>
, whereCommandResult
is defined as
/// Combined execution state of an `RxCommand`
/// Will be issued for any state change of any of the fields
/// During normal command execution, you will get this item's listening at the command's [.results] observable.
/// 1. If the command was just newly created, you will get `null, false, false` (data, error, isExecuting)
/// 2. When calling execute: `null, false, true`
/// 3. When exceution finishes: `result, false, false`
class CommandResult<T> {
final T data;
final Exception error;
final bool isExecuting;
const CommandResult(this.data, this.error, this.isExecuting);
bool get hasData => data != null;
bool get hasError => error != null;
@override
bool operator ==(Object other) =>
other is CommandResult<T> && other.data == data && other.error == error && other.isExecuting == isExecuting;
@override
int get hashCode => hash3(data.hashCode, error.hashCode, isExecuting.hashCode);
@override
String toString()
{
return 'Data: $data - HasError: $hasError - IsExecuting: $isExecuting';
}
}
The .results Observable is especially useful if you want to feed the result of a Command directly to a StreamBuilder
. This will display different contents depending on the state of the command's execution, and it works very well with RxLoader
from the rx_widgets
package. Here is an example of an RxLoader
widget that uses the .results
Observable:
Expanded(
/// RxLoader executes different builders based on the
/// state of the Stream<CommandResult>
child: RxLoader<List<WeatherEntry>>(
spinnerKey: AppKeys.loadingSpinner,
radius: 25.0,
commandResults: sl.get<AppManager>().updateWeatherCommand.results,
/// executed if .hasData == true
dataBuilder: (context, data) =>
WeatherListView(data, key: AppKeys.weatherList),
/// executed if .isExceuting == true
placeHolderBuilder: (context) => Center(
key: AppKeys.loaderPlaceHolder, child: Text("No Data")),
/// executed if .hasError == true
errorBuilder: (context, ex) => Center(
key: AppKeys.loaderError,
child: Text("Error: ${ex.toString()}")),
),
),
Creating RxCommands
RxCommands can wrap synchronous and asynchronous functions. Functions that:
- Take no parameter and don't return any value
- Take a Parameter and don't return a result value
- Take no Parameter but return a value
- Take a parameter and return a value
For the different variations of possible handler functions RxCommand offers several factory methods for synchronous and asynchronous handlers. They look like this.
/// Creates a RxCommand for a synchronous handler function with no parameter and no return type
/// `action`: handler function
/// `canExecute` : observable that can bve used to enable/diable the command based on some other state change
/// if omitted the command can be executed always except it's already executing
static RxCommand<void, void> createSyncNoParamNoResult(Action action,[Observable<bool> canExecute])
There are these variants:
static RxCommand<TParam, TResult> createSync<TParam, TResult>(Func1<TParam, TResult> func,...
static RxCommand<void, TResult> createSyncNoParam<TResult>(Func<TResult> func,...
static RxCommand<TParam, void> createSyncNoResult<TParam>(Action1<TParam> action,...
static RxCommand<void, void> createSyncNoParamNoResult(Action action,...
static RxCommand<TParam, TResult> createAsync<TParam, TResult>(AsyncFunc1<TParam, TResult> func,...
static RxCommand<void, TResult> createAsyncNoParam<TResult>(AsyncFunc<TResult> func,...
static RxCommand<TParam, void> createAsyncNoResult<TParam>(AsyncAction1<TParam> action,...
static RxCommand<void, void> createAsyncNoParamNoResult(AsyncAction action,...
Even if your wrapped function does not return a value, RxCommand will emit a void
value when the function has executed. So you can listen to such a command to react when the function has finished
Accessing the last result
RxCommand.lastResult
gives you access to the last successful result value of the commands execution. Which can be uses as initialData
of a StreamBuilder.
If you want to get the last result included in the CommandResult
events while executing or in case of an error you can pass emitInitialCommandResult=true
when creating the command.
If you want to assign an initialValue to .lastResult
e.g. if you use it with a StreamBuilder's
initialData
you can pass it with the initialLastResult
parameter when creating the command.
Example - Making Flutter Reactive
The latest version of the https://github.com/escamoteur/making_flutter_reactive- repository was refactored to RxVMS, so now you should have a good example on how to use it.
Because it's a very simple app, we only need one Manager:
class AppManager {
RxCommand<String, List<WeatherEntry>> updateWeatherCommand;
RxCommand<bool, bool> switchChangedCommand;
RxCommand<String, String> textChangedCommand;
AppManager() {
// This Command expects a bool value when executed and passes it through on it's
// result Observable (stream)
switchChangedCommand = RxCommand.createSync<bool, bool>((b) => b);
// We pass the result of switchChangedCommand as canExecute Observable to
// the updateWeatherCommand
updateWeatherCommand = RxCommand.createAsync<String, List<WeatherEntry>>(
sl.get<WeatherService>().getWeatherEntriesForCity,
canExecute: switchChangedCommand,
);
// Will be called on every change of the search field
textChangedCommand = RxCommand.createSync<String, String>((s) => s);
// When the user starts typing
textChangedCommand
// Wait for the user to stop typing for 500ms
.debounce(new Duration(milliseconds: 500))
// Then call the updateWeatherCommand
.listen(updateWeatherCommand);
// Update data on startup
updateWeatherCommand('');
}
}
You can combine different RxCommands together. Notice how switchedChangedCommand
is actually the canExecute
Observable for updateWeatherCommand
.
Now observe how the Manager is used in the UI:
return Scaffold(
appBar: AppBar(title: Text("WeatherDemo")),
resizeToAvoidBottomPadding: false,
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
key: AppKeys.textField,
autocorrect: false,
controller: _controller,
decoration: InputDecoration(
hintText: "Filter cities",
),
style: TextStyle(
fontSize: 20.0,
),
// Here we use the textChangedCommand!
onChanged: sl.get<AppManager>().textChangedCommand,
),
),
Expanded(
/// RxLoader executes different builders based on the
/// state of the Stream<CommandResult>
child: RxLoader<List<WeatherEntry>>(
spinnerKey: AppKeys.loadingSpinner,
radius: 25.0,
commandResults: sl.get<AppManager>().updateWeatherCommand.results,
dataBuilder: (context, data) => WeatherListView(data, key: AppKeys.weatherList),
placeHolderBuilder: (context) => Center(key: AppKeys.loaderPlaceHolder, child: Text("No Data")),
errorBuilder: (context, ex) => Center(key: AppKeys.loaderError, child: Text("Error: ${ex.toString()}")),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
/// Building the Updatebutton depending on updateWeatherCommand.canExecute
Expanded(
// This might be solved with a Streambuilder too but it should show `WidgetSelector`
child: WidgetSelector(
buildEvents: sl
.get<AppManager>()
.updateWeatherCommand
.canExecute, //We access our ViewModel through the inherited Widget
onTrue: RaisedButton(
key: AppKeys.updateButtonEnabled,
child: Text("Update"),
onPressed: () {
_controller.clear();
sl.get<AppManager>().updateWeatherCommand();
},
),
onFalse: RaisedButton(
key: AppKeys.updateButtonDisabled,
child: Text("Please Wait"),
onPressed: null,
),
),
),
// This Switch toggles the canExecuteInput of the updadateWeatherCommand
StateFullSwitch(
state: true,
onChanged: sl.get<AppManager>().switchChangedCommand,
),
],
),
),
],
),
);
Typical usage patterns
We have already seen one way to react to the different states of a command using CommandResults
. In cases where we want to display if the command has succeeded (but not display the result), a common pattern is to listen to the command's Observables in the initState
function of a StatefulWidget
. Here is an example of a real project.
createEventCommand
is defined as:
RxCommand<Event, void> createEventCommand;
It will create an Event object in the database and return no real value. But as we have learned before, even an RxCommand with return type void
will emit one data item on completion. So we can use this behaviour to trigger an action in our App as soon as the command completes:
@override
void initState() {
// this subscription just waits for the completion of the command and then pops the page and shows a Toast
_eventCommandSubscription = _createCommand.listen((_) async {
Navigator.pop(context);
await showToast('Event saved');
});
// react on any error while creating an event
_errorSubscription = _createEventCommand.thrownExceptions.listen((ex) async {
await sl.get<ErrorReporter>().logException(ex);
await showMessageDialog(context, 'There was a problem saving event', ex.toString());
});
}
Important: we have to cancel this subscriptions when we don't need them anymore:
@override
void dispose() {
_eventCommandSubscription?.cancel();
_errorSubscription?.cancel();
super.dispose();
}
Also, if you want to use an overlay to display a busy spinner, you could:
- listen to the
isExceuting
Observable of a command in theinitState
function; - show/hide the spinner in the subscription; and
- use the Command itself as the data source for a StreamBuilder
Making life easier with RxCommandListeners
If you want to react to more than one Observable, you may have to manage multiple subscriptions. Directly managing the listening and freeing of multiple subscriptions can be challenging, makes the code less readable, and puts you at risk for making mistakes (e.g. forgetting to cancel during dispose
).
The latest version of rx_command
added a helper class called RxCommandListener
, which is designed to makes this handling easier. Its constructor takes a command and direct handler functions for the different state changes:
class RxCommandListener<TParam, TResult> {
final RxCommand<TParam, TResult> command;
// Is called on every emitted value of the command
final void Function(TResult value) onValue;
// Is called when isExceuting changes
final void Function(bool isBusy) onIsBusyChange;
// Is called on exceptions in the wrapped command function
final void Function(Exception ex) onError;
// Is called when canExecute changes
final void Function(bool state) onCanExecuteChange;
// is called with the vealue of the .results Observable of the command
final void Function(CommandResult<TResult> result) onResult;
// to make the handling of busy states even easier these are called on their respective states
final void Function() onIsBusy;
final void Function() onNotBusy;
// optional you can directly pass in a debounce duration for the values of the command
final Duration debounceDuration;
RxCommandListener(this.command,{
this.onValue,
this.onIsBusyChange,
this.onIsBusy,
this.onNotBusy,
this.onError,
this.onCanExecuteChange,
this.onResult,
this.debounceDuration,}
)
void dispose();
You don't have to pass all of the handler functions. They all are optional, so you can just pass the ones you need. You only have to dispose
the RxCommandListener
in your dispose
function, and it will cancel all of the internally-used subscriptions.
Let's compare the same code with and without RxCommandListener
in another real app example. The selectAndUploadImageCommand
here is used in a chat screen where the user can upload images to the chat. When the command is called:
- An
ImagePicker
dialog is shown. - After an image has been selected, the image is uploaded.
- Once upload is complete, the command returns the storage location of the image so that a new image chat entry can be created.
Without RxCommandListener
:
_selectImageCommandSubscription = sl
.get<ImageManager>()
.selectAndUploadImageCommand
.listen((imageLocation) async {
if (imageLocation == null) return;
// this calls the execute method of the command
sl.get<EventManager>().createChatEntryCommand(new ChatEntry(
event: widget.event,
isImage: true,
content: imageLocation.downloadUrl,
));
});
_selectImageIsExecutingSubscription = sl
.get<ImageManager>()
.selectAndUploadImageCommand
.isExecuting
.listen((busy) {
if (busy) {
MySpinner.show(context);
} else {
MySpinner.hide();
}
});
_selectImageErrorSubscription = sl
.get<ImageManager>()
.selectAndUploadImageCommand
.thrownExceptions
.listen((ex) => showMessageDialog(context, 'Upload problem',
"We cannot upload your selected image at the moment. Please check your internet connection"));
With RxCommandListener
:
selectImageListener = RxCommandListener(
command: sl.get<ImageManager>().selectAndUploadImageCommand,
onValue: (imageLocation) async {
if (imageLocation == null) return;
sl.get<EventManager>().createChatEntryCommand(new ChatEntry(
event: widget.event,
isImage: true,
content: imageLocation.downloadUrl,
));
},
onIsBusy: () => MySpinner.show(context),
onNotBusy: MySpinner.hide,
onError: (ex) => showMessageDialog(context, 'Upload problem',
"We cannot upload your selected image at the moment. Please check your internet connection"));
As a rule of thumb, I would only use an RxCommandListener if I wanted to listen to more than one observable.
Give RxCommands a try and see how it can make your live easier. You don't need to use RxVMS to benefit from the power of RxCommands.
For additional information about RxCommand read the readme of the rx_command
package.
Unfortunately I'm pretty busy in a client project at the moment so that I can't say when the next post will be ready, so please have a bit patience.
Contact me:
Hi Thomas,
Thanks for the great blog.
I am a beginner to Flutter and I am trying to grasp your architecture. It will take some time I believe:)
One quick question: Is GetIt an alternative to BLOC method? Or is it mainly a cool singleton architecture?
I was started with BLOC(sink-stream) pattern and i was getting used to it. I read your rxdart blogs which were really helpful and was planning on using rxdart in BLOC. Then I saw about GetIt. Now I am a little confused about where GetIt stands? If it is an alternative to BLOC/inherited widget/scoped model/etc, what are the main pros/cons?
Thank you very much.
GetIt is not an replacement for BLOC but it can be used to access your BLOCs from the UI in this regard it is a replacement for en InheritedWidget/ScopedModel. It also has the advantage that it is completely separated from any UI code
Yeaaah!!! that’s what I called have fun on the belt . waiting for more
is there an equivalent of RxCommand in Javascript?
i’d like to use RxVMS paradigm with React too
I don’t think so. I fear you will have to implement it on your own
Great post! Looking forward your next post and more examples.
Thanks very much.
Thanks a lot! I try when I find time
Cool this what I waiting for so long, I already read your previous series in I am in love with. so excited for the upcoming post! 🙂
Hi Thomas!
Glad to see you continue with this excellent series. Looking forward to more.
One question about RxCommand. In some cases my manager wants to expose a command that is only callable from within the manager but its result stream is exposed for clients to listen to.
Before RxCommand I would make my trio of subject, private sink and public stream. How to model that with RxCommand?
Sure I can just have the public RxCommand but this will allow code outside the manager to call the command and I want to disallow this.
Any advice?
Thanks!!
What you want is actually pretty easy, just expose the Observable interface of the command by adding a public getter getter like Observable get publicStream => myCommand;
Just coming from Xamarin and ReactiveUI as a beginner.
Can’t believe I might be able to get the best of both worlds. Great post, great package and great expectations!
Thanks a lot, I hope you will enjoy Flutter as I do
k, lol really easy to read code and App flow If I use RxVMS. I hav been using normal Streambuilders in my current app(using Blocs) with using Navigate inside build(keep getting error) Do u think Rxloader will let me use Navigator inside its builder??? Either way I ll use this pattern for my App cause of higher Modularity.
Hi,
no you won’t be able to use Navigator inside, but I recommend using an RxCommand and listening in initstate where you can use the Navigator
After reading all of your posts about RxVAMS, RxVMS and watching your video I am sure its the architecture to go with flutter. It is easy to learn, read the code and understand whats going on and why.
Thanks for your blogs and great packages (RxCommand, RxWidget and GetIt).
Thanks a lot for your kind words
Where do you hold state data like current user. For example, an app stores the user locally. User is authenticated through some authentication service which is fine. But how does one get access to that state data over and over again? Rather then through a command, should the user be accessed as a variable? On the Manager (e.g. UserManager).
I also found that I have a UserService (to retrieve additional info about user), SecurityService (to authenticate the user) and “Data”Service (to retrieve business data for the app based on the logged in user). No I could have three different Managers but then I have to switch between then depending on what data I need to access. So I rather went with an AppManager which in turn calls these three services. Would that be the suggested pattern?
To your first question, I typically use have a
currentUser
property in my UserManager.Sure you could use one single AppManager but I prefer to split them into separate Managers to keep them at a reasonable code size.
Hi Thomas!
I have translated all of 4 posts of this series. All with link back of course.
https://habr.com/ru/post/448776/
https://habr.com/ru/post/450950/
https://habr.com/ru/post/451292/
https://habr.com/ru/post/449872/
Is there any chance to see rest of planned ones? ))
Best regards!
I just started reading your posts an am really interested in the approach you’re taking. Thanks for sharing this with everyone.
I actually started using redux as I love ngrx for angular and ionic apps. Ngrx is built around rxjs, and you get so many cool built-in features. I don’t totally agree with your opinion on redux, but I’m also not happy with it’s current implementation in Flutter.
Because of my disappointing first steps in Flutter redux, I’m going to look to switch to your approach, so keep the awesome content coming!
Max
Hi Max, I guess when you are already comfortable with redux then redux is ok. I personally just dont like it.
Thanks for the nice words.
Thomas
Hi.. I’m interesting to implement RxVMS for my very first project, but i’m new in flutter and android. I’ve no idea how to create auth/login page with php and mysql as my backend. Would you like to give me some tutorial, please?
Thank you in advance for any help you can provide
Hi, I’m sorry but I don’t have the time to guide you through the development of your backend. Also I recommend to use FirebaseAuth as backend instead of rolling your on in PHP.
Hi again, Thomas!
It’s really pity that you have no time to continue this series..
it was started very promisingly and i hope you will continue…
Best regards!
More is coming I promise
Hi sir… I tried to implement your great code architecture in my project, But I
ve faced a question since I'm new to reactive programming. I
ve posted it on StackOverFlow.I would be glad if you can make time and answer it since it’s based on your posts.
https://stackoverflow.com/questions/57978973/dart-processing-rxcommand-result-before-send-it-to-rxloader
Hi Thomas,
Thanks for the awesome work you are doing. I personally use RxCommands in my project and love them. Hope you will continue this article series.
Alex
Thanks a lot!
Sad I am late to this party.!!
I feel RxCommand is a cleaner alternative for dealing with multiple stream based interactions. I am currently using manually created streams to interact with my JS based plots and over time this has clogged my widgets with more code for stream management.
Down the line will try to refactor the code base and give you a feedback. Thanks a lot for sharing this meaningful approach. I use GetIt in my project mainly as singleton locator. I will have to rewrite that as well for future changes like, incorporating a better native charting library for flutter… 🙂
Thanks. I’m glad you like it. Make sure to read my latest post on GetIt
Hey Thomas!
Thank you very much for this great architecture and the nice to read articles! Even as a rookie I was able to understand them.
I’m planning my first flutter project and I’m planning to use RxVMS. Is there any additional information to the videos and these blog posts available?
Are you planning to continue these blog posts on RxVMS? It would mean a lot to me!
Thank you from Hamburg
Marcel
Hi Marcel,
yes they will be continued and updated, I just can’t say when.
Cheers
Thomas
Great article!
Can you point out an example project using REST API with RxCommand? There has to be a VSCode Project Template for Flutter using this approach!
Have you checked out the example app in the rx_command repo?
Actually I recommend having a look at my new package flutter_command which doesn’t work with streams but with ValueListenables. Together with the package functional_listener, get_it and get_it_mixin you get a really powerful combination.
as an excample checkout https://github.com/escamoteur/RVMS-2020