Slide 1

Slide 1 text

KotlinConf 2018 Android ฤ Akira Iwaya, LINE Corp. 1

Slide 2

Slide 2 text

Who am I • ؠ୩໌ɹ(@hoshi_gaki) • LINE LIVE Android • ϦϚΠϯ͘Μ • αʔόʔαΠυKotlinͷ஌ݟʗKotlin ͰClova Skill Award௅ઓ0 0 https://engineering.linecorp.com/ja/blog/detail/346 2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

LINE LIVE • 30% Kotlin and increasing • In LINE app as submodule • MVVM + Partially Redux • Dagger2, RxJava2, AutoDispose, ExoPlayer, ... 4

Slide 5

Slide 5 text

ϦϚΠϯ͘Μ • Reminder bot • Almost Kotlin • Spring Boot 2 • MyBatis + MySQL, Redis • App(1) + DB(1) • 452K friends 5

Slide 6

Slide 6 text

Android sessions at KotlinConf 2018 • Android Suspenders (Christ Banes) • Shaping your app's architecture with Kotlin and Architecture Components • Android KTX: A dash of Kotlin makes all the difference! 6

Slide 7

Slide 7 text

7

Slide 8

Slide 8 text

Android Suspenders2 Christ Bane • Why coroutines • How to use coroutines in Android • Jobs, Scopes, Reactivity • Cancellations, Exceptions, Dispatchers, Channels (skip) • Android callback -> suspend functions (skip) 2 https://chris.banes.me/talks/2018/android-suspenders/ 8

Slide 9

Slide 9 text

• Android's main thread: 60Hz, 16ms / 90Hz, 12ms / 120Hz, 8ms • AsyncTask, Executors, Loaders, Future, Rx, Coroutine 9

Slide 10

Slide 10 text

So why coroutines? • Great for I/O tasks • Scale on limited hardware • Easier development model 10

Slide 11

Slide 11 text

kotlinx-coroutines-core kotlinx-coroutines-android kotlinx-coroutines-rx2 11

Slide 12

Slide 12 text

How to use coroutines in Android class ShowDetailsViewModel: ViewModel() { val showRepository: ShowRepository = // ... val state: LiveData init { refresh() } private fun refresh() { launch { showRepository.updateShow(showId) // update view state } } } 12

Slide 13

Slide 13 text

Cancellation class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null private fun refresh() { updateShowJob = launch { showRepository.updateShow(showId) // update view state } } override fun onCleared() { updateShowJob?.cancel() } } 13

Slide 14

Slide 14 text

! class ShowDetailsViewModel: ViewModel() { private var updateShowJob: Job? = null private var updateEpisodesJob: Job? = null private var updateCastJob: Job? = null private fun refresh() { updateShowJob = launch { ... } updateEpisodesJob = launch { ... } updateCastJob = launch { ... } } override fun onCleared() { updateShowJob?.cancel() updateEpisodesJob?.cancel() updateCastJob?.cancel } } 14

Slide 15

Slide 15 text

Jobs can have parent-child relationship ! private val job = Job() private fun refresh() { launch(parent = job) { showRepository.updateShow(showId) // update view state } } override onCleared() { job.cancel() } 15

Slide 16

Slide 16 text

launch(parent = job) { updateShow(showId) } suspend fun updateShow(showId: Long) { val local = async { // no parent localShowStore.getShow(showId) } val remote = async [ // no parent remoteSource.getShow(showId) ] val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) } 16

Slide 17

Slide 17 text

CoroutineScope • Two coroutines does not get cancelled when parent job is cancelled ! • launch, async is deprecated in v0.26 • launch, async becomes instance method of CoroutineScope • Must provide default context 17

Slide 18

Slide 18 text

class ShowDetailsViewModel: ViewModel(), CoroutineScope { private val job = Job() override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main private fun refresh() { launch(context = coroutineContext) { showRepository.updateShow(showId) ... } } override onCleared() { super.onCleared() job.cancel() } } 18

Slide 19

Slide 19 text

! suspend fun updateShow(showId: Long) = coroutineScope { val local = async { // will be cancelled on job.cancel() localShowStore.getShow(showId) } val remote = async [ // will be cancelled on job.cancel() remoteSource.getShow(showId) ] val merged = mergeShow(local.await(), remote.await()) localShowStore.saveShow(merged) } 19

Slide 20

Slide 20 text

Reactivity Coroutines can replace Single / Maybe / Completable interface RemoteService { @GET("/trendingshows") fun trendingShows(): Single> } service.trendingShows() .scheduleOn(Schedulers.io()) .subscribe(::onTrendingLoaded, ::onError) 20

Slide 21

Slide 21 text

With kotlinx-coroutines-rx2 interface RemoteService { @GET("/trendingshows") fun trendingShows(): Single> } val shows = withContext(IO) { service.trendingShows().await() } 21

Slide 22

Slide 22 text

Coming to Retrofit soon interface RemoteService { @GET("/trendingshows") suspend fun trendingShows(): List } val shows = withContext(IO) { service.trendingShows() } 22

Slide 23

Slide 23 text

Related sessions • Exploring Coroutines in Kotlin3 • Kotlin Coroutines in Practice4 • Coroutines and Reactive Programming - friends or foes?5 5 https://youtu.be/yoLh4sd1CWI 4 https://youtu.be/a3agLJQ6vt8 3 https://youtu.be/jT2gHPQ4Z1Q 23

Slide 24

Slide 24 text

24

Slide 25

Slide 25 text

Shaping your app's architecture with Kotlin and Architecture Components6 Florina Muntenescu • Why using coroutines • Where to using coroutines • How to using coroutines • Java • Type Safety - Inner class (Kotlin 1.3) 6 https://youtu.be/Sy6ZdgqrQp0 25

Slide 26

Slide 26 text

Why using coroutines • Ease of learning • Single/Maybe/Completable -> suspend fun • Streams of data (Channel) 26

Slide 27

Slide 27 text

Where to use coroutines 27

Slide 28

Slide 28 text

28

Slide 29

Slide 29 text

How to using coroutines - API retrofit2-kotlin-coroutines-adapter @POST("api/v2/comments") fun comment(@Body comment: NewCommentRequest): Deferred> • RemoteDataSource returns Result sealed class Result { data class Success(val data: T): Result() data class Error(val exception: Exception): Result } 29

Slide 30

Slide 30 text

class CommentsRemoteDataSource(private val Service: RetrofitService) { suspend fun comment(commentBody: String) = safeApiCall( call = { postComment(commentBody) }, errorMessage = "Unable to post commment" ) } suspend fun safeAPiCall( call: suspend () -> Result // suspend lambda errorMessage: String ): Result = return try { call() } catch(e: Exception) { Result.Error(IOException(errorMessage, e)) } 30

Slide 31

Slide 31 text

Java 31

Slide 32

Slide 32 text

class ShotsRepository(private val dataSource: XXXDataSource) { suspend fun search(query: String): Result> = remoteDataSource.search(query) } 32

Slide 33

Slide 33 text

shotsRepository.search(query, new Continuation>>() { @NotNull @Override public CoroutineContext getContext() { return null; } @Override public void resume(Result> result) {} @Override public void resumeWithException(@NotNull Throwable throwable) {} } 33

Slide 34

Slide 34 text

A Good enough compromise - Callback class ShotsRepository(private val dataSource: XXXDataSource) { private val inflight = mutableMapOf() fun search( query: String, onResult: (Result>) -> Unit) : Result> { inflight[id] = launchSearch(query, onResult) } fun cancel() { inflight.values.forEach { it.cancel() } inflight.clear() } } 34

Slide 35

Slide 35 text

Type Safety - Inline Class class CommentsRepository(...) { suspend fun postStoryComment( body: String, storyId: Long, userId: Long ): Result { ... } suspend fun postReplyComment( body: String, parentCommentId: Long, userId: Long ): Result

Slide 36

Slide 36 text

Type Safety - Inline Class class CommentsRepository(...) { suspend fun postStoryComment( body: String, storyId: StoryId, userId: Long ): Result { ... } suspend fun postReplyComment( body: String, parentCommentId: CommentId, userId: Long ): Result

Slide 37

Slide 37 text

data class StoryId(val id: Long) • Extra object creation occurs (Runtime overhead) • Primitive types are heavily optimized by the runtime inline class StoryId(val id: Long) • From Kotlin 1.3 • An inline class must have a single property initialized in the primary constructor 37

Slide 38

Slide 38 text

UseCase - invoke operator • A use case should have single responsibility • UseCase should have only one public method class PostStoryCommentUseCase(repository: XXXRepository) { suspend fun postStoryComment(body: String, id: Long): Result } 38

Slide 39

Slide 39 text

• By implementing invoke operator • Callers doesn't care how this is implemented, even nor method name class PostStoryComment(...) { suspend operator fun invoke(body: String, id: Long): Result } // Caller side val postStoryComment = ... val result = postStoryComment(comment, id) 39

Slide 40

Slide 40 text

bit.ly/livedata-events 40

Slide 41

Slide 41 text

Modular, extensible architecture based on the architecture components and Kotlin 41

Slide 42

Slide 42 text

42

Slide 43

Slide 43 text

Android KTX: A dash of Kotlin makes all the difference!7 Dan Kim • KTX is 1.0 with Android X (skip) • Various extensions (1ݸ) • How to contribute AOSP (skip) 7 https://www.youtube.com/watch?v=LP3PaPrIFHo 43

Slide 44

Slide 44 text

// added in API level 11 view.addOnLayoutChangeListener(object: View.OnLayoutChangeListener { override fun onLayoutChange( view: View? left: Int, top: Int, right: Int, bottom: Int, oldTop: Int, oldRight: Int, oldBottom: Int ) { doSomething() } }) 44

Slide 45

Slide 45 text

// KTX view.doOnNextLayout { doSomething() } 45

Slide 46

Slide 46 text

46