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

Улучшаем жизнь Android разработчика с
Kotlin Coroutines и ViewModel

Улучшаем жизнь Android разработчика с
Kotlin Coroutines и ViewModel

Сайт конференции: https://kolesa-conf.kz

Процесс разработки становится сложнее с каждым годом, из-за отсутствия единого подхода. Это привело к появлению архитектурных компонентов (AAC), в которые входит библиотека ViewModel для реализации шаблона проектирования MVVM, а так же стабильная версия Kotlin Coroutine для решения асинхронных задач в программировании. Расскажу, как с помощью этих простых библиотек упростить жизнь Android разработчиков и как данный подход помогает снизить порог вхождения в команду Kolesa Group.

Zhanibek Marshal

April 12, 2019
Tweet

More Decks by Zhanibek Marshal

Other Decks in Programming

Transcript

  1. !2 Жанибек Маршал Ведущий Android разработчик Веду технический блог janibekmarshal.com


    Выкладываю код в github.com/johnmars
 Пишу обо всем в twitter.com/megasuperhero Обращайтесь сюда t.me/orangerange
  2. Что вы узнаете !5 1. Как получить данные для отображения

    2. Применение Kotlin Coroutines 3. Как Реализовать MVVM архитектуру
  3. Что вы узнаете !6 1. Как получить данные для отображения

    2. Применение Kotlin Coroutines 3. Как Реализовать MVVM архитектуру 4. Полезные советы
  4. Объявление !7 • Заголовок: Текст • Цена: Текст • Описание:

    Текст • Адрес и дата: Текст • Количество фотографии: Цифры
  5. !23 Запускаем асинхронный код launch { val searchResult = withContext(Dispatchers.IO)

    { repository.search(limit = 20) } } По умолчанию вызывается главный поток (UI Thread)
  6. !24 Запускаем асинхронный код launch { val searchResult = withContext(Dispatchers.IO)

    { repository.search(limit = 20) } } Запускаем фоновый поток (Worker Thread)
  7. !25 Запускаем асинхронный код launch { val searchResult = withContext(Dispatchers.IO)

    { repository.search(limit = 20) } } Получаем данные с Repository
  8. !28 Линейное исполнение кода launch { // Главный поток progressBar.visibility

    = View.VISIBLE val advertisements = withContext(Dispatchers.IO) { // Фоновый поток repository.search(limit = 20) } }
  9. !29 Линейное исполнение кода launch { // Главный поток progressBar.visibility

    = View.VISIBLE val advertisements = withContext(Dispatchers.IO) { // Фоновый поток repository.search(limit = 20) } // Главный поток adapter.submitList(advertisements) progressBar.visibility = View.INVISIBLE }
  10. !32 class AdvertisementsActivity : ..., CoroutineScope { override val coroutineContext:

    CoroutineContext get() = Dispatchers.Main } CoroutineScope
  11. !33 class AdvertisementsActivity : ..., CoroutineScope { private val job

    = Job() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job } CoroutineScope
  12. !39 Main Context IO Context Все еще идет загрузка
 объявлении...

    Восстановление Списка объявлении
  13. !40 Main Context IO Context Все еще идет загрузка
 объявлении...

    Восстановление Списка объявлении
  14. !44 Отмена работы Coroutines override fun onDestroy() { super.onDestroy() job.cancel()

    } private fun loadAdvertisement(id: Long) { launch { val advertisement = withContext(Dispatchers.IO) { repository.getAdvertisement(id) } showAdvertisement(advertisement) } } IO Context
  15. !45 Отмена работы Coroutines override fun onDestroy() { super.onDestroy() job.cancel()

    } private fun loadAdvertisement(id: Long) { launch { val advertisement = withContext(Dispatchers.IO) { repository.getAdvertisement(id) } showAdvertisement(advertisement) } } UI Context
  16. !46 Отмена работы Coroutines override fun onDestroy() { super.onDestroy() job.cancel()

    } private fun loadAdvertisement(id: Long) { launch { val advertisement = withContext(Dispatchers.IO) { repository.getAdvertisement(id) } showAdvertisement(advertisement) } }
  17. !48 interface AdvertisementRepository { suspend fun search( limit: Int =

    20, offset: Int = 0 ): List<Advertisement> } Suspend
  18. Отображение данных !56 var showAdvertisements: ((List<Advertisement>) -> Unit)? = null

    private val advertisements = mutableListOf<Advertisement>() fun search() { launch { val searchResults = withContext(Dispatchers.IO) { ... } advertisements.addAll(searchResults) } }
  19. Отображение данных !57 var showAdvertisements: ((List<Advertisement>) -> Unit)? = null

    private val advertisements = mutableListOf<Advertisement>() fun search() { launch { val searchResults = withContext(Dispatchers.IO) { ... } advertisements.addAll(searchResults) showAdvertisements?.invoke(advertisements) } }
  20. Отображение данных !58 var showAdvertisements: ((List<Advertisement>) -> Unit)? = null

    set(value) { field = value value?.invoke(advertisements) } fun search() { launch { ... showAdvertisements?.invoke(advertisements) } }
  21. Отображение данных !59 var showAdvertisements: ((List<Advertisement>) -> Unit)? = null

    private val advertisements = mutableListOf<Advertisement>() fun search() { launch { val searchResults = withContext(Dispatchers.IO) { ... } advertisements.addAll(searchResults) } } Реализовывать будет View
  22. Отображение данных !60 var showAdvertisements: ((List<Advertisement>) -> Unit)? = null

    private val advertisements = mutableListOf<Advertisement>() fun search() { launch { val searchResults = withContext(Dispatchers.IO) { ... } advertisements.addAll(searchResults) } } Реализовывать будет View
  23. Применяем LiveData !62 val liveData = MutableLiveData<MutableList<Advertisement>>().apply { value =

    mutableListOf() } fun search() = launch { val searchResult = withContext(Dispatchers.IO) { repository.search(limit = 20) } liveData.value = liveData.value?.apply { this.addAll(searchResult) } }
  24. !65 Отмена работы Во ViewModel class AdvertisementsViewModel : ViewModel(), CoroutineScope

    { private val job = Job() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override onCleared() { job.cancel() } }
  25. !66 class AdvertisementsViewModel : ViewModel(), CoroutineScope { private val job

    = Job() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override onCleared() { job.cancel() } } class AdvertisementsViewModel : ViewModel() { } dependencies { implementation 'androidx.lifecycle 2.1.0' } Без Android X Android X
  26. View + ViewModel !67 • View уведомляет о событиях на

    экране • ViewModel хранит данные для View
  27. !79 View ViewModel Repository Data Source Событие Фильтр Отображение Обработка

    UI UI/IO IO Main/IO Запрос Database, API, Firebase, GPS и т.д.
  28. !81 suspendCoroutine override suspend fun search( limit: Int, offset: Int

    ): List<Advertisement> { return suspendCancellableCoroutine { continuation -> dataSource.search(callback = { continuation.resume(it) }) } }
  29. !82 suspendCoroutine override suspend fun search( limit: Int, offset: Int

    ): List<Advertisement> { return suspendCancellableCoroutine { continuation -> dataSource.search(callback = { continuation.resume(it) }) } }
  30. !83 suspendCoroutine override suspend fun search( limit: Int, offset: Int

    ): List<Advertisement> { return suspendCancellableCoroutine { continuation -> dataSource.search(callback = { continuation.resume(it) }) } }
  31. Одиночные события для LiveData !85 То что отображается один раз

    И больше не показывается при восстановление данных
  32. !92 SingleObserver class KolesaMutableLiveData<T> : MutableLiveData<T>() { private val observers:

    MutableMap< LifecycleOwner, MutableSet<SingleObserverWrapper> > = ConcurrentHashMap() ... }
  33. !93 SingleObserver class KolesaMutableLiveData<T> : MutableLiveData<T>() { private val observers:

    MutableMap< LifecycleOwner, MutableSet<SingleObserverWrapper> > = ConcurrentHashMap() ... }
  34. !96 Как комбинировать разные типы данных в один список? Объявление

    Объявление Объявление Объявление Реклама
  35. !98 Объявление Объявление Объявление Объявление Реклама ViewModel LiveData List<Поисковый результат>

    Subscribe Notify MediatorLiveData Объявление Объявление Объявление Объявление Реклама
  36. !100 runBlocking @Test fun `Should search advertisements then show them`()

    { runBlocking { whenever(advertisementRepository.search()) .thenReturn(...) } ... } Suspend Function
  37. !101 runBlocking @Test fun `Should search advertisements then show them`()

    { runBlocking { whenever(advertisementRepository.search()) .thenReturn(...) } ... } Блокирует CoroutineContext
  38. Итоги !103 1. Как реализовывать код 
 с presentation layer

    до data layer 2. Coroutines помогает легче управлять 
 состоянием потока и читать PR
  39. Итоги !104 1. Как реализовывать код 
 с presentation layer

    до data layer 2. Coroutines помогает легче управлять 
 состоянием потока и читать PR 3. Важно определить архитектуру заранее
  40. Итоги !105 1. Как реализовывать код 
 с presentation layer

    до data layer 2. Coroutines помогает легче управлять 
 состоянием потока и читать PR 3. Важно определить архитектуру заранее 4. Появляются множество дополнений связанных 
 с Coroutines и Architecture Components
  41. Ссылки • Developer Android - LiveData • Develop Android -

    ViewModel • DroidKaigi 2019 - LiveData ͱ Coroutines Ͱ࣮૷͢Δ DDD ͷઓज़తઃܭ / Yuki Anzai [JA] • Understanding Kotlin Coroutines: ίϧʔνϯͰਐԽ͢ΔΞϓϦέʔγϣϯ։ ൃ • LiveData with SnackBar, Navigation and other events • Architecture Components - I'm not a purist but … • View actions (snackbar, activity navigation, ...) in ViewModel !107