Modernize the development of LINE Messenger for Android

Modernize the development of LINE Messenger for Android

Masakuni Oishi
LINE App Dev Team1 Senior Software Engineer
https://linedevday.linecorp.com/jp/2019/sessions/B2-4

Be4518b119b8eb017625e0ead20f8fe7?s=128

LINE DevDay 2019

November 21, 2019
Tweet

Transcript

  1. 2019 DevDay Modernize the Development of LINE Messenger for Android

    > Masakuni Oishi > LINE App Dev Team1 Senior Software Engineer
  2. Communication app “LINE” Initial release in June 2011

  3. Released in 2011 December 2010 - Android 2.3 Gingerbread February

    2011 - Android 3.0 Honeycomb June 2011 - LINE 1.0 October 2011 - Android 4.0 Ice Cream Sandwich November 2011 - Galaxy Nexus
  4. Fragment Support library AndroidX Architecture components Jetpack Runtime permissions Dynamic

    feature module MediaCodec Kotlin Multi-window App Bundle Play Services
  5. Keep up with changes in the development environment

  6. Agenda > Kotlin and Kotlin Coroutines > Multi-module project

  7. Kotlin
 in LINE Android app

  8. Kotlin in LINE Android App > April 2017 - Start

    using Kotlin for unit tests > Then, improves our Coding Conventions • Adding some rules to the official Coding Conventions • e.g. Prefer “.also { }” than “.apply { }” • e.g. Don’t omit types of properties. > June 2017 - Start using Kotlin in production • New code should be written in Kotlin • When adding new features to Java code, convert the code to Kotlin first
  9. Kotlin in LINE Android App > The number of code

    in LINE Android app Java code 6200 files (53%) 1048k lines (64%) Kotlin code 5450 files (47%) 593k lines (36%) (As of October 2019. Not including test code.)
  10. Benefits of Kotlin > 100% interoperable with Java > Android

    Studio (IntelliJ IDEA) has a Java-to-Kotlin converter > null safety > Immutable collections > property delegation (lazy), extensions, data classes, sealed classes, etc. > Coroutines ← !!
  11. Kotlin Coroutines > Simplifies asynchronous tasks • Can be used

    in procedural-way rather than functional-way (e.g. RxJava) • No callback-hell > Similar to async/await in C#, JavaScript > Structured concurrency & Managed cancellation > Developed as experimental for a long while > Oct 2018 - Kotlin Coroutines 1.0.0 → We couldn’t adopt it when we started using Kotlin (April 2017)
  12. Async Libraries Used Before > LINE Android app has a

    lot of async code, and the code was written in many kinds of libraries… • android.os.AsyncTask • java.util.concurrent.Executor + callbacks • RxJava 1.x • RxJava 2.x • Our own async library • etc…
  13. Transition Plan to Kotlin Coroutines > We want to unify

    these async libraries. > But until recently, we couldn’t adopt Kotlin Coroutines. → First, we adopted RxJava2 entirely. → Then, transit it to Kotlin Coroutines later. Kotlin Coroutines has a utility library that allows Coroutines and RxJava2 to be used each other.
  14. RxJava2 → Kotlin Coroutines > To facilitate the transition to

    Kotlin Coroutines, functions that perform asynchronous tasks using RxJava2 should return Single<T>. > We call such functions as “async functions”. // async function fun fooAsync(param: Param): Single<Result> = …
  15. Example of Async Function // blocking function @WorkerThread fun fetchJsonBlocking(url:

    Url): Json = … // async function fun fetchJsonAsync(url: Url): Single<Json> = Single.fromCallable { fetchJsonBlocking(url) }.subscribeOn(Schedulers.io())
  16. Call an Async Function val disposable = fetchJsonAsync(url) .observeOn(AndroidSchedulers.Main()) .subscribe

    { json -> updateUi(json) }
  17. Call Multiple Async Functions // another async function fun getFooDataFor(json:

    Json): Single<FooData> = … val disposable = fetchJsonAsync(url) .flatMap { json -> getFooDataFor(json) }.observeOn(AndroidSchedulers.Main()) .subscribe { fooData -> showFooData(fooData) }
  18. Async Functions Can Be Easily Migrated to Coroutines

  19. Call an Async Function From Coroutine mainScope.launch { val json

    = fetchJsonAsync(url).await() updateUi(json) } > By simply adding “.await()”, you can call async functions from Coroutines.
  20. Call Multiple Async Functions From Coroutine mainScope.launch { val json

    = fetchJsonAsync(url).await() val fooData = getFooDataFor(json).await() showFooData(fooData) } > By using Coroutines, you can write multiple calls of async functions procedurally.
  21. Migrate Async Functions to Suspending Functions > Finish the transition

    to Coroutines by rewriting async functions with suspending functions. // suspending function suspend fun foo(param: Param): Result = … > Suspending functions can be called without “.await()”.
  22. Example of Suspending Function // async function fun fetchJsonAsync(url: Url):

    Single<Json> = Single.fromCallable { fetchJsonBlocking(url) }.subscribeOn(Schedulers.io()) // suspending function suspend fun fetchJson(url: Url): Json = withContext(Dispatchers.IO) { fetchJsonBlocking(url) }
  23. Cancellation of Coroutines > When using RxJava in Android apps,

    we usually cancel subscriptions of Rx when Activities / Fragments are stopped. > So, we implemented a utility class to cancel Coroutines on Activity/ Fragment’s onStop(). AutoResetLifecycleScope https://github.com/line/lich/tree/master/lifecycle
  24. class FooFragment : Fragment() { private val coroutineScope: CoroutineScope =

    AutoResetLifecycleScope(this) fun fetchAndShowJson() { coroutineScope.launch { try { val json = fooService.fetchJson() updateUi(json) } catch (e: IOException) { Toast.makeText(…).show() } } }
  25. What We Did To Introduce Coroutines > Wrote documents to

    introduce Kotlin Coroutines through RxJava2. > Implemented some utility classes for cancellation of Coroutines. > The utility classes and sample code using the MVVM pattern are published on GitHub: https://github.com/line/lich
  26. Multi-module project
 for LINE Android app

  27. Why Is Modularization Necessary? > Decouple code for each feature

    • Extensibility - Easy to understand the scope of impact on changes. > Speed up the build time • LINE Android app has more than 1.6M lines! > Reduce the initial APK size on installation • By using Dynamic Feature Modules + on-demand delivery
  28. app

  29. app Foo feature

  30. Extract Facade of Features > To split a feature as

    a module, extract its Facade class. > A feature only exposes its Facade (and a few parameter classes). • The feature must be accessed through the Facade only. class FooFacade { fun launchFooActivity(messageId: String) { … } … }
  31. app Foo feature

  32. FooFacade app Foo feature

  33. Split Facade into Interface and Implementation interface FooFacade { fun

    launchFooActivity(messageId: String) } class FooFacadeImpl : FooFacade { override fun launchFooActivity(messageId: String) = … } > Other features only need to know the Facade interface.
  34. FooFacade app Foo feature

  35. FooFacade FooFacadeImpl app Foo feature

  36. FooFacade FooFacadeImpl app module Foo module

  37. How To Get the Instance of the Facade? > The

    instance of the Facade is needed to call the feature. > How about using Dependency Injection (DI)? • Existing DI libraries such as Dagger2 are very complex to set up for multi-module projects. > So, we developed a library that easily solves the dependency problem in multi-module projects. • https://github.com/line/lich/tree/master/component val fooFacade: FooFacade = … ←??? fooFacade.launchFooActivity(messageId)
  38. interface FooFacade { fun launchFooActivity(messageId: String) companion object : ComponentFactory<FooFacade>()

    { override fun createComponent(context: Context): FooFacade = delegateToServiceLoader(context) } } @AutoService(FooFacade::class) class FooFacadeImpl : FooFacade { override fun launchFooActivity(messageId: String) = … } Resolve Dependencies of Facades
  39. Call Feature Through its Facade class OtherModuleActivity : Activity() {

    // `by component()` provides the instance of FacadeImpl. private val fooFacade by component(FooFacade) fun startFooFeature(messageId: String) { fooFacade.launchFooActivity(messageId) } }
  40. FooFacade FooFacadeImpl app module Foo module Resolve dependency

  41. FooFacade FooFacadeImpl app module Foo module BarFacade BazFacade BarFacadeImpl BazFacadeImpl

    Bar module Baz module
  42. How To Modularize Android Project > Extract the Facade interface

    of a feature. > Let other modules call the feature only through the Facade. > Split the implementation of the feature as a module. > Use “Lich Component” library to get the instance of a Facade. • https://github.com/line/lich/tree/master/component
  43. About Lich > Lich is an OSS library collection for

    Android apps. • Developed by LINE Android client team. https://github.com/line/lich > Let’s check it out, and please star it!
  44. Thank you