Upgrade to Pro — share decks privately, control downloads, hide ads and more …

KotlinConf 2018 報告会 Android 編

KotlinConf 2018 報告会 Android 編

Akira Iwaya

October 19, 2018
Tweet

More Decks by Akira Iwaya

Other Decks in Programming

Transcript

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

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

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

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

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

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

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

    on limited hardware • Easier development model 10
  10. 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
  11. 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
  12. ! 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. ! 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
  18. 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
  19. Coming to Retrofit soon interface RemoteService { @GET("/trendingshows") suspend fun

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

  22. 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
  23. 28

  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. • 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
  33. 42

  34. 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
  35. // 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
  36. 46