TranslateProject/sources/tech/20171006 Create a Clean-Code App with Kotlin Coroutines and Android Architecture Components.md
lctt-bot 260969e631 Revert "bestony is translating."
This reverts commit b37cf2203e.
2019-08-15 17:00:17 +00:00

15 KiB
Raw Blame History

Create a Clean-Code App with Kotlin Coroutines and Android Architecture Components

Full demo weather app included.

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 its 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 with reactive-first mindset?

The Cycle.js library (by André Staltz) contains a great explanation of reactive-first mindset: Cycle.jsStreams.

Rx is highly composable and it has great potential, but its 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)

  • Should we use experimental Kotlin Coroutines? (which, again, promote totally new programming style)

  • Should we use the new experimental library from Google: Android Architecture Components?

Its necessary to try it all first in a small app to really make an informed decision. This is exactly what I did, getting some useful insights in the process. If you want to find out what I learned, read on!

About The App

The aim of the experiment was to create an app that downloads weather data for cities selected by user and then displays forecasts with graphical charts (and some fancy animations). Its simple, yet it contains most of the typical features of Android projects.

It turns out that coroutines and architecture components play really well together and give us clean app architecture with good separation 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 mindeven 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 chainsthese chains can quickly become too  _functional. _ ;-)

Having said that, I dont think that RxJava can be replaced with coroutines in every use case. Observables give us a different kind of expressiveness that 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 belowbut beware, Im not a designer. :-) Chart animations show how easily you can implement them arbitrarily by hand with simple coroutinewithout any ObjectAnimators, Interpolators, Evaluators, PropertyValuesHolders, etc.

** 此处有Canvas,请手动处理 **

** 此处有iframe,请手动处理 **

The most important source code snippets are displayed below. However, if youd like to see the full project, its available on GitHub.

https://github.com/elpassion/crweather

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 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 computationmore about it in my next blog post.

01 Weather Service

This service downloads weather forecasts for a given city from Open Weather Map REST API.

I use simple but powerful library from Square called Retrofit. I guess by now every Android developer knows it, but in case you never used it: its the most popular HTTP client on Android. It makes network calls and parses responses to POJO. Nothing fancy herejust a typical Retrofit configuration. I plug in the Moshi converter to convert JSON responses to data classes.

https://github.com/elpassion/crweather/…/OpenWeatherMapApi.kt

One important thing to note here is that I set a return types of functions generated by Retrofit to: Call.

I use Call.enqueue(Callback) to actually make a call to Open Weather Map. I dont use any call adapter provided by Retrofit, because I wrap the Call object in the  suspendable function  myself.

02 Utils

This is where we enter the (brave newcoroutines  world: we want to create a generic  suspendable function  that wraps a Call object.

I assume you know at least the very basics of coroutines. Please read the first chapter of Coroutines Guide (written by Roman Elizarov) if you dont.

It will be an extension function:  suspend  fun Call.await() that invokes the Call.enqueue(…) (to actually make a network call), then  suspends  and later  resumes  (when the response comes back).

** 此处有Canvas,请手动处理 **

https://github.com/elpassion/crweather/…/CommonUtils.kt

To turn any asynchronous computation into a  suspendable function,  we use the suspendCoroutine function from The Kotlin Standard Library. It gives us a Continuation object which is kind of a universal callback. We just have to call its resume method (or resumeWithException) 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  fun Call.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) displayed in our app.

https://github.com/elpassion/crweather/…/Repository.kt

Here we have some private  suspendable functions  created by applying our  suspend  fun Call.await() extension to weather service functions. This way all of them return ready to use data like Forecast instead of Call. Then we use it in our one public  suspendable functionsuspend  fun getCityCharts(city: String): List. It converts the data from api to a ready to display list of charts. I use some custom extension properties on List to actually convert the data to List. Important note: only  suspendable functions  can call other  suspendable functions .

We have the appid hardcoded here for simplicity. Please generate new appid hereif you want to test the appthis 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 app model (implementing the Android ViewModel 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 : ViewModel used by our one activity: MainActivity.

https://github.com/elpassion/crweather/…/MainModel.kt

This class represents the app itself. It will be instantiated by our activity (actually by the Android system ViewModelProvider), but it will survive configuration changes such as a screen rotationnew activity instance will get the same model instance. We dont have to worry about activity lifecycle here at all. Instead of implementing all those activity lifecycle related methods (onCreate, onDestroy, …), we have just one onCleared() method called when the user exits the app.

To be precise onCleared method is called when the activity is finished.

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 comes into play.

The LiveData is like RxJava BehaviorSubject reinvented once again… It holds a mutable value that is observable. The most important difference is how we subscribe to it and we will see it later in the MainActivity.

Also LiveData doesnt have all those powerful composable operators Observable has. There are only some simple Transformations.

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 class.

In here we are actually using the MutableLiveData : LiveData objects that allow to push new values into it freely. The app state is represented by four LiveData objects: city, charts, loading, and message. The most important of these is the charts: LiveData<List> 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 itselfyou 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 doesnt block the thread at all.

Stay tuned for my next post, where I will explain  actors  (and  channels ) in detail.


via: https://blog.elpassion.com/create-a-clean-code-app-with-kotlin-coroutines-and-android-architecture-components-f533b04b5431

作者:Marek Langiewicz 译者:译者ID 校对:校对者ID

本文由 LCTT 原创编译,Linux中国 荣誉推出