Android development is evolving fast. A lot of developers and companies are trying to address common problems and create some great tools or libraries that can totally change the way we structure our apps.
We get excited by the new possibilities, but it’s difficult to find time to rewrite our app to really benefit from a new programming style. But what if we actually start a new project? Which of those breakthrough ideas to employ? Which solutions are stable enough? Should we use RxJava extensively and structure our app withreactive-first mindset?
> The Cycle.js library (by[André Staltz][6]) contains a great explanation of reactive-first mindset:[Cycle.js—Streams][7].
Rx is highly composable and it has great potential, but it’s so different from regular object-oriented programming style, that it will be really hard to understand for any developer without RxJava experience.
There are more questions to ask before starting a new project. For example:
* Should we use Kotlin instead of Java?
(actually here the answer is simple:[YES][1])
* Should we use experimentalKotlin Coroutines? (which, again, promote totally new programming style)
* Should we use the new experimental library from Google:
Android Architecture Components?
It’s necessary to try it all first in a small app to really make an informed decision. This is exactly what[I did][8], getting some useful insights in the process. If you want to find out what I learned, read on!
### About[TheApp][9]
The aim of the experiment was to create an[app][10]that downloads weather data for cities selected by user and then displays forecasts with graphical charts (and some fancy animations). It’s simple, yet it contains most of the typical features of Android projects.
It turns out thatcoroutinesandarchitecture componentsplay really well together and give us clean app architecture with goodseparation of concerns. Coroutines allow to express ideas in a natural and concise way. Suspendable functions are great if you want to code line-by-line the exact logic you have in mind—even if you need to make some asynchronous calls in between.
Also:no more jumping between callbacks. In this example app, coroutines also completely removed the need of using RxJava. Functions with suspendable points are easier to read and understand than some RxJava operator chains—these chains can quickly become too_functional._ ;-)
> Having said that, I don’t think that RxJava can be replaced with coroutines in every use case. Observables give us adifferent kind of expressivenessthat can not be mapped one to one to suspendable functions. In particular once constructed observable operator chain allow many events to flow through it, while a suspendable point resumes only once per invocation.
Back to our weather app:
You can watch it in action below—but beware, I’m not a designer.:-)
Chart animations show how easily you can implement them arbitrarily by hand with simple coroutine—without any ObjectAnimators, Interpolators, Evaluators, PropertyValuesHolders, etc.
** 此处有Canvas,请手动处理 **
** 此处有iframe,请手动处理 **
The most important source code snippets are displayed below. However, if you’d like to see the full project, it’s available[onGitHub.][11]
[https://github.com/elpassion/crweather][12]
There is not a lot of code and it should be easy to go through.
I will present the app structure starting from the network layer. Then I will move to the business logic (in the[MainModel.kt][13]file) which is_(almost)_not Android-specific. And finish with the UI part (which obviously is Android-specific).
Here is the general architecture diagram with text reference numbers added for your convenience. I will especially focus on_green_elements—_suspendable functions_and_actors_(an actor is a really useful kind of_coroutine builder_ ).
> The actor model in general is a mathematical model of concurrent computation—more about it in my next blog post.
This service downloads weather forecasts for a given city from[Open Weather Map][14]REST API.
I use simple but powerful library from[Square][15]called[Retrofit][16]. I guess by now every Android developer knows it, but in case you never used it: it’s the most popular HTTP client on Android. It makes network calls and parses responses to[POJO][17]. Nothing fancy here—just a typical Retrofit configuration. I plug in the[Moshi][18]converter to convert JSON responses to data classes.
One important thing to note here is that I set a return types of functions generated by Retrofit to:[Call][19].
I use[Call.enqueue(Callback)][20]to actually make a call to Open Weather Map. I don’t use any[call adapter][21]provided by Retrofit, because I wrap the Call object in the_suspendable function_myself.
### 02 Utils
This is where we enterthe ([brave new][22])_coroutines_world: we want to create a generic_suspendable function_that wraps a[Call][23]object.
> I assume you know at least the very basics of coroutines. Please read the first chapter of[Coroutines Guide][24](written by[Roman Elizarov][25]) if you don’t.
It will be an extension function: [_suspend_ funCall<T>.await()][26]that invokes the[Call.enqueue(…)][27](to actually make a network call), then_suspends_and later_resumes_(when the response comes back).
To turn any asynchronous computation into a_suspendable function,_we use the[suspendCoroutine][28]function from The Kotlin Standard Library. It gives us a[Continuation][29]object which is kind of a universal callback. We just have to call its[resume][30]method (or[resumeWithException][31]) anytime we want our new_suspendable function_to resume (normally or by throwing an exception).
The next step will be to use our new_suspend_funCall<T>.await()function to convert asynchronous functions generated by Retrofit into convenient_suspendable functions_ .
### 03 Repository
The Repository object is a source of the data ([charts][32]) displayed in our app.
Here we have some private_suspendable functions_created by applying our_suspend_funCall<T>.await()extension to weather service functions. This way all of them return ready to use data like Forecast instead of Call<Forecast>. Then we use it in our one public_suspendable function_ :_suspend_fungetCityCharts(city: String): List<Chart>. It converts the data from api to a ready to display list of charts. I use some custom extension properties on List<DailyForecast> to actually convert the data to List<Chart>. Important note: only_suspendable functions_can call other_suspendable functions_ .
> We have the[appid][33]hardcoded here for simplicity. Please generate new appid[here][34]if you want to test the app—this hardcoded one will be automatically blocked for 24h if it is used too frequently by too many people.
In the next step we will create the main appmodel(implementing the Android[ViewModel][35]architecture component), that uses an_actor (coroutine builder)_to implement the application logic.
### 04 Model
In this app we only have one simple model:[MainModel][36]:[ViewModel][37]used by our one activity:[MainActivity][38].
This class represents the app itself. It will be instantiated by our activity (actually by the Android system[ViewModelProvider][39]), but it will survive configuration changes such as a screen rotation—new activity instance will get the same model instance. We don’t have to worry about activity lifecycle here at all. Instead of implementing all those activity lifecycle related methods (onCreate, onDestroy, …), we have just oneonCleared()method called when the user exits the app.
> To be preciseonClearedmethod is called when the activity isfinished.
Even though we are not tightly coupled to activity lifecycle anymore, we still have to somehow publish current state of our app model to display it somewhere (in the activity). This is where the[LiveData][40]comes into play.
The[LiveData][41]is like[RxJava][42][BehaviorSubject][43]reinvented once again… It holds a mutable value that isobservable. The most important difference is how we subscribe to it and we will see it later in the[MainActivity][44].
> Also LiveData doesn’t have all those powerful composable operators Observable has. There are only some simple[Transformations][45].
> Another difference is that LiveData is Android-specific and RxJava subjects are not, so they can be easily tested with regular non-android JUnit tests.
> Yet another difference is that LiveData is “lifecycle aware”—more about it in my next posts, where I present the[MainActivity][46]class.
In here we are actually using the[MutableLiveData][47]:[LiveData][48]objects that allow to push new values into it freely. The appstateis represented by four LiveData objects:city,charts,loading, andmessage. The most important of these is thecharts: LiveData<List<Chart>>object which represents current list of charts to display.
All the work of changing the app state and reacting to user actions is performed by an_ACTOR_ .
_Actors_are awesome and will be explained in my next blog post:-)
### Summary
We have already prepared everything for our main_actor_ . And if you look at the_actor_code itself—you can (kind of) see how it works even without knowing_coroutines_or_actors_theory. Even though it has only a few lines, it actually contains all important business logic of this app. The magic is where we call_suspendable functions_(marked by gray arrows with green line). One_suspendable point_is the iteration over user actions and second is the network call. Thanks to_coroutines_it looks like synchronous blocking code but it doesn’t block the thread at all.
Stay tuned for my next post, where I will explain_actors_(and_channels_ ) in detail.