Pro Yearly is on sale from $80 to $50! »

KotlinConf 2018 報告会 Android 編

KotlinConf 2018 報告会 Android 編

99dec28d69a44e5b554e9c3f360e280e?s=128

Akira Iwaya

October 19, 2018
Tweet

Transcript

  1. KotlinConf 2018 Android ฤ Akira Iwaya, LINE Corp. 1

  2. Who am I • ؠ୩໌ɹ(@hoshi_gaki) • LINE LIVE Android •

    ϦϚΠϯ͘Μ • αʔόʔαΠυKotlinͷ஌ݟʗKotlin ͰClova Skill Award௅ઓ0 0 https://engineering.linecorp.com/ja/blog/detail/346 2
  3. 3

  4. LINE LIVE • 30% Kotlin and increasing • In LINE

    app as submodule • MVVM + Partially Redux • Dagger2, RxJava2, AutoDispose, ExoPlayer, ... 4
  5. ϦϚΠϯ͘Μ • Reminder bot • Almost Kotlin • Spring Boot

    2 • MyBatis + MySQL, Redis • App(1) + DB(1) • 452K friends 5
  6. 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
  7. 7

  8. 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
  9. • Android's main thread: 60Hz, 16ms / 90Hz, 12ms /

    120Hz, 8ms • AsyncTask, Executors, Loaders, Future, Rx, Coroutine 9
  10. So why coroutines? • Great for I/O tasks • Scale

    on limited hardware • Easier development model 10
  11. kotlinx-coroutines-core kotlinx-coroutines-android kotlinx-coroutines-rx2 11

  12. How to use coroutines in Android class ShowDetailsViewModel: ViewModel() {

    val showRepository: ShowRepository = // ... val state: LiveData<ViewState> init { refresh() } private fun refresh() { launch { showRepository.updateShow(showId) // update view state } } } 12
  13. 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
  14. ! 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. ! 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
  20. Reactivity Coroutines can replace Single / Maybe / Completable interface

    RemoteService { @GET("/trendingshows") fun trendingShows(): Single<List<Show>> } service.trendingShows() .scheduleOn(Schedulers.io()) .subscribe(::onTrendingLoaded, ::onError) 20
  21. With kotlinx-coroutines-rx2 interface RemoteService { @GET("/trendingshows") fun trendingShows(): Single<List<Show>> }

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

    trendingShows(): List<Show> } val shows = withContext(IO) { service.trendingShows() } 22
  23. 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
  24. 24

  25. 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
  26. Why using coroutines • Ease of learning • Single/Maybe/Completable ->

    suspend fun • Streams of data (Channel) 26
  27. Where to use coroutines 27

  28. 28

  29. How to using coroutines - API retrofit2-kotlin-coroutines-adapter @POST("api/v2/comments") fun comment(@Body

    comment: NewCommentRequest): Deferred<Response<PostCommentResponse>> • RemoteDataSource returns Result<TResponse> sealed class Result<out T: Any> { data class Success<out T: Any>(val data: T): Result<T>() data class Error(val exception: Exception): Result<Nothing> } 29
  30. class CommentsRemoteDataSource(private val Service: RetrofitService) { suspend fun comment(commentBody: String)

    = safeApiCall( call = { postComment(commentBody) }, errorMessage = "Unable to post commment" ) } suspend fun <T: Any> safeAPiCall( call: suspend () -> Result<T> // suspend lambda errorMessage: String ): Result<T> = return try { call() } catch(e: Exception) { Result.Error(IOException(errorMessage, e)) } 30
  31. Java 31

  32. class ShotsRepository(private val dataSource: XXXDataSource) { suspend fun search(query: String):

    Result<List<Shot>> = remoteDataSource.search(query) } 32
  33. shotsRepository.search(query, new Continuation<Result<? extends List<Shot>>>() { @NotNull @Override public CoroutineContext

    getContext() { return null; } @Override public void resume(Result<? extends List<Shot>> result) {} @Override public void resumeWithException(@NotNull Throwable throwable) {} } 33
  34. A Good enough compromise - Callback class ShotsRepository(private val dataSource:

    XXXDataSource) { private val inflight = mutableMapOf<String, Job>() fun search( query: String, onResult: (Result<List<Shot>>) -> Unit) : Result<List<Shot>> { inflight[id] = launchSearch(query, onResult) } fun cancel() { inflight.values.forEach { it.cancel() } inflight.clear() } } 34
  35. Type Safety - Inline Class class CommentsRepository(...) { suspend fun

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

    postStoryComment( body: String, storyId: StoryId, userId: Long ): Result<CommentResponse> { ... } suspend fun postReplyComment( body: String, parentCommentId: CommentId, userId: Long ): Result<CommentResponse { ... } } 36
  37. 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
  38. 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<Comment> } 38
  39. • 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<Comment> } // Caller side val postStoryComment = ... val result = postStoryComment(comment, id) 39
  40. bit.ly/livedata-events 40

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

    41
  42. 42

  43. 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
  44. // 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
  45. // KTX view.doOnNextLayout { doSomething() } 45

  46. 46