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

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

LINE DevDay 2019

November 21, 2019
Tweet

More Decks by LINE DevDay 2019

Other Decks in Technology

Transcript

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

    > Masakuni Oishi > LINE App Dev Team1 Senior Software Engineer
  2. 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
  3. Fragment Support library AndroidX Architecture components Jetpack Runtime permissions Dynamic

    feature module MediaCodec Kotlin Multi-window App Bundle Play Services
  4. 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
  5. 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.)
  6. 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 ← !!
  7. 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)
  8. 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…
  9. 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.
  10. 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> = …
  11. 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())
  12. 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) }
  13. 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.
  14. 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.
  15. 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()”.
  16. 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) }
  17. 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
  18. 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() } } }
  19. 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
  20. 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
  21. app

  22. 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) { … } … }
  23. 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.
  24. 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)
  25. 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
  26. 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) } }
  27. 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
  28. 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!