As a developer, we believe that maintaining multiple code bases doing the same thing is painful, that’s why Jetbrains come up with Kotlin Multiplatform Project, Google with Flutter and Facebook with React Native. Kotlin Multiplatform aim to solve business logic part while the later aim to solve end to end; start from business logic all the way to User Interface. I tried Kotlin Multiplatform before, while it has some limitation with Kotlin Native and Coroutines, it’s working as expected. You can check my article here:
https://medium.com/@fandygotama/kotlin-multiplatform-reactive-a45263e1fd7a
For my next journey, i decided to give it a go with Flutter. Yes i know Flutter has Flutter for Web, why bother using AngularDart? Well, at present Flutter for Web is still in developer preview, and not recommended by Google to use in production. The future is unknown, and we live in the present. So, let’s try using what is available today 😁
If you want to read and do the code at the same time, this article will take about 30 minutes to 1 hour to finish.
Note:
I won’t go into detail explaining about Flutter’s Widget, AngularDart and how reactive programming works, as you easily find all the documentations from Google.
In this tutorial, at least you have:
StatefulWidget
and StatelessWidget
works,tour_of_heroes
tutorial from Google,Our goal is to create shareable business logic written in pure dart for Flutter and AngularDart. Our app will make connection to OMDb, get the movie list and show the result in Flutter and Web.
This is our final result.
This tutorial will use MVVM pattern with input output approach inspired by kickstarters, (https://github.com/kickstarter/native-docs/blob/master/inputs-outputs.md).
Here’s a design diagram how our app structure will be.
For this tutorial, i’m not using clean architecture approach as it will make the tutorial more complicated than it should be.
At the time of writing, i am using the latest version of the following libraries:
Flutter SDK: 1.12.2 dev channel
Dart SDK: 2.7.0
RxDart: 0.22.6
http: 0.12.0+2
AngularDart: 6.0.0-alpha 1
AngularComponent: 0.14.0-alpha 1
Android Studio 3.5.2 (Flutter)
VS Code 1.40.1 (AngularDart)
We’re going to use Android Studio to write our business logic. Fire up Android Studio and select New Flutter Project…, and choose Flutter Application
Set project name, e.g.: flutter_shareable
and click Next
Now set the package name, check AndroidX
, Kotlin
support and Swift
support, and click Finish.
Now that we have Flutter application, we’re going to create the library, select File -> New -> New Flutter Project and select Flutter Package. This will be our Pure Dart implementation.
Set project name, e.g.: core
, and set project location inside flutter_shareable
directory, and click Finish.
Below is our project directory structure:
Add dependencies into your Flutter’s pubspec.yaml
file.
Connecting to cloud service
We will connect to OMDb and get the movie list. API is free, you can get it from here:
https://www.omdbapi.com/apikey.aspx
We are going to search all movies contain avenger keyword with this url: http://www.omdbapi.com/?s=avenger&apikey=xxxxxx
Below are the JSON responses from the API:
Now, lets start preparing our data model.
Open up core directory, remove core.dart
and core_test.dart
, create new directory called model
, and create new file movie.dart
Its recommended to use lowercase and _ when creating Dart file.
Create two new files called mapper.dart
and movie_mapper.dart
inside lib
directory.
How it works
Movie
model,Mapper
, this interface purpose is simple, to transform JSON String into object model,MovieMapper
Next step, we will get movie list from API using http library. Open up core’s pubspec.yaml
, add http as dependencies and remove all flutter dependencies (IMPORTANT).
If you got an error when importing http or other libraries in core’s module, try go to core’s directory and run flutter pub get via terminal manually.
Create 2 files: service.dart
and movie_cloud_service.dart
How it works
ViewModel
. You can use this interface to get data from cloud or from local cache (for example),apiKey
and search query. The result from http is JSON String, which is then passed to our mapper interface and return an object model; in this case a movie model.ViewModel
Now that we’ve finished preparing http service, we’re going to write ViewModel
class. This class purpose will be the main gateway to stream data between UI and Service back and forth.
Add RxDart
(if you haven’t) dependencies into core’s pubspect.yaml
, then create 2 new classes called list_view_model.dart
, and get_movie_list_view_model.dart
How it works
start
and loadMore
, and 3 outputs: loading
, result
and exception
. By only providing this information to our UI platform, we are able to separate the concern and class responsibilities. UI simply call inputs and get the outputs, thats it!_loadingProperty.stream
into loading observable
and_exceptionProperty.stream
into exception observable
. the code is pretty self explanatory. Now we move to result observable; here we start by getting _startProperty
input and show loading indicator by calling _loadingProperty.sink.add(true)
, next we translate our future data we’re getting from service into observable by calling Observable.fromFuture
. After that we hide the loading indicator by calling _loadingProperty.sink.add(false)
. The _loadMoreProperty
is pretty much has the same logic, call loading, translate future to observable and hide loading. The only differences is that we set the clearItems variable to false in _loadMoreProperty.doOnData
as we do not want to reset the list during load more scrolling. Last we called merge to merge both streams and return list of movies.BehaviorSubject
. Why not PublishSubject
?, i’ll explain later.dispose()
method and clear all of our property to prevent leak.observable.sink.add
is equivalent with observable.onNext
in other reactive language. doOnData
is equivalent with doOnNext
.
Main
Now lets move to our UI, open up main.dart
, and remove the default example code, replace with below code:
How it works
ViewModel
and provide all the dependencies needed by the ViewModel
. You can also use dependency injection to provide the ViewModel
.ViewModel
into MovieList’s widget.Creating Movie List Widget
Next we create movie list widget and it’s item widget. Create 2 files called movie_list.dart
and movie_cell.dart
How it works
StreamBuilder
to subscribe to our ViewModel’s outputs. In this example we only subscribe into 1 output, that is: widget.viewModel.outputs.result
. In real application, we need to also subscribe to widget.viewModel.outputs.exception
to pass the error message to user, or widget.viewModel.outputs.loading
to show loading indicator,BehaviorSubject
instead of PublishSubject
is because our input can start running, even before we subscribe to the output. in this case: widget.viewModel.inputs.start
is called first before subscription happens. Using PublishSubject
will show empty screen when you load the screen. Try changing BehaviorSubject
into PublishSubject
, remark WidgetsBinding
inside initState()
, and move widget.viewModel.inputs.start
into initState()
. You won’t see the movie list in your simulator screen, changing back to BehaviorSubject
will resolve this issue.Run your Flutter App and you should see the similar output from our Goal Section.
Do not forget to call dispose()
during onDestroy()
to prevent memory leak!
Now let’s work on AngularDart. clone the quickstart project from Google repo:
https://github.com/angular-examples/quickstart/tree/master
copy to flutter_shareable project and rename it to angular
Your directory structure should be like this:
Open up VS Code, and select angular directory (do not select flutter project), openpubspec.yaml
, addcore library
, and update all dependencies to the latest version.
Next, we will provide our ViewModel
via Angular’s dependency injection. Create a new directory called src
and di
as subdirectory, add new file called movie_module.dart
Now, open app_component.dart
, and add the following code:
How it works
GetMovieListViewModel
,http.Client
and MovieMapper
along with key and host information because our CloudMovieService
need that information. Next we use FactoryProvider
to provide Service and ViewModel
respectively,ViewModel
into AppComponent
constructor.ngOnInit
we start subscribing to viewModel.outputs.result
and viewModel.outputs.loading
, and pass the result into List<Movie>
movies and bool loading
, which will be used by app_component.html
,ngOnDestroy()
viewModel.outputs.result.listen
is equivalent to viewModel.outputs.result.subscribe(onNext:)
in other reactive languages.
AngularDart provide AsyncPipe
to work on Observable
. However during testing, using AsyncPipe
caused my html page to run in infinite loop. After doing some research (with google of course), i’ve found out this article explaining why it happens. This article is in Japanese, however you can use google translate like i did 😁
Thats why i decided to not use AsyncPipe
and listen to Observable
manually.
HTML & CSS
This is not my expertise as i haven’t touched both for a very long time, however to complete this tutorial lets continue.
Create app_component.css
inside lib
directory
And create app_component.html
in the same directory.
How it works
ngSwitch
, if its true, show loading text, otherwise show default text,Run using webdev serve
, and you should see the layout as displayed in our Goal section.
If you’re getting build error after updating dependencies, simply close the webdev, run webdev build
and then re-run webdev serve
.
Code sharing between platform is the future. If you follow my Kotlin Multiplatform tutorial you should see the same exact business logic in ViewModel using Reactive approach, i believed somewhere in the near future, we could make this perfect and become benefit for everybody.
This tutorial may become obsolete when Flutter for Web is ready for production use, but until then, i hope this will help you to achieve single code base, multi platform.
Least but not last, code is available here: