From 3c7d0939a90b51615a059cebbcfa0cbc58debdf3 Mon Sep 17 00:00:00 2001 From: Maxim Bachinsky Date: Tue, 15 Sep 2020 19:17:01 +0300 Subject: [PATCH 01/10] added docs for navigation-cicerone module --- navigation-cicerone/README.md | 26 ++++++++++++-- .../navigation_cicerone/flow/FlowFragment.kt | 35 +++++++++++-------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/navigation-cicerone/README.md b/navigation-cicerone/README.md index bca41e11..a7bc2762 100644 --- a/navigation-cicerone/README.md +++ b/navigation-cicerone/README.md @@ -1,4 +1,24 @@ -navigation-cicerone -==== +# navigation-cicerone -TODO: rewrite dependencies +Набор базовых классов для настройки навигации на основе библиотеки cicerone + +## Основные классы и интерфейсы + +### FlowFragment, FlowNavigation, FlowNavigationModule + +Для использования Single-Activity подхода возникают ситуации, когда несколько экранов нужно объединить одной родительской сущностью. +Объединение необходимо для хранения общего dagger компонента на flow или организации собственной навигации. +Раньше можно было использовать Activity. Но в Single-Activity должно быть только одно активити на приложение. В такой архитектуре можно использовать +parent fragment для всех экран - FlowFragment. FlowFragment обладает своей навигацией. Для навигации используется childFragmentManager этого фрагмента. + +Чтобы использовать эту навигацию в di, в модуль добавлены dagger-модуль FlowNavigationModule, который держит основные cicerone сущности, и именная +аннотация FlowNavigation. + +### CiceroneTuner + +CiceroneTuner наследует класс LifecycleObserver для упрощенной работы с NavigatorHolder. Данный класс в onResume добавляет navigator в NavigatorHolder +и в onPause удаляет navigator из NavigatorHolder. + +## Дополнительные материалы + +- [Статья, которая объясняет Single-Activity подход](https://habr.com/ru/company/redmadrobot/blog/426617/) diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt index 08903be3..4d2ed53b 100644 --- a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt +++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt @@ -14,6 +14,12 @@ import ru.touchin.mvi_arch.core_nav.R import ru.touchin.roboswag.navigation_cicerone.CiceroneTuner import javax.inject.Inject +/** + * Base parent fragment for fragments of hole feature. FlowFragment has own navigator based on childFragmentManager. + * FlowFragment is responsible for handling of back button press. + * + * You should connect FlowNavigationModule to your Dagger component and add inject method for your flow fragment. + */ abstract class FlowFragment : Fragment(R.layout.fragment_flow) { @Inject @@ -24,6 +30,18 @@ abstract class FlowFragment : Fragment(R.layout.fragment_flow) { @FlowNavigation lateinit var router: Router + private val exitRouterOnBackPressed = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + router.exit() + } + } + + open fun createNavigator(): Navigator = SupportAppNavigator( + requireActivity(), + childFragmentManager, + getFragmentContainerId() + ) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) injectComponent() @@ -32,14 +50,6 @@ abstract class FlowFragment : Fragment(R.layout.fragment_flow) { } } - abstract fun injectComponent() - - private val exitRouterOnBackPressed = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - router.exit() - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycle.addObserver( @@ -49,14 +59,11 @@ abstract class FlowFragment : Fragment(R.layout.fragment_flow) { requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, exitRouterOnBackPressed) } - open fun createNavigator(): Navigator = SupportAppNavigator( - requireActivity(), - childFragmentManager, - getFragmentContainerId() - ) - @IdRes protected fun getFragmentContainerId(): Int = R.id.flow_parent + abstract fun injectComponent() + abstract fun getLaunchScreen(): SupportAppScreen + } From b04efe307c8f13eed5c10ff05763b275f2c46644 Mon Sep 17 00:00:00 2001 From: Maxim Bachinsky Date: Tue, 15 Sep 2020 19:29:31 +0300 Subject: [PATCH 02/10] added base docs for pagination module --- pagination/README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pagination/README.md b/pagination/README.md index 7ff06b06..8331b04f 100644 --- a/pagination/README.md +++ b/pagination/README.md @@ -1,4 +1,14 @@ -pagination -==== +# pagination -TODO: rewrite dependencies +Модуль для добавления списка элементов с постраничной загрузкой. + +## Основные классы + +### Paginator +Класс наследуется от Store из модуля mvi-arch. Стейт-машина, которая отвечает за изменение состояния списка элементов. + +### PaginationView +View, которая отвечает за отображение постраничного списка. Основной метод - render, который принимает на вход Paginator.State. View состоит из +SwipeRefreshLayout и Switcher на 3 состояния: loading, error/empty, success. Success state состоит из RecyclerView, который работает с PaginationAdapter. + +### PaginationAdapter From 7b3b6aa2c0571103a8a7a3bb1878266441aeffcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D1=87?= =?UTF-8?q?=D0=B8=D0=BD=D1=81=D0=BA=D0=B8=D0=B8=CC=86?= Date: Wed, 16 Sep 2020 14:43:39 +0300 Subject: [PATCH 03/10] added russian docs for pagination' classes --- pagination/README.md | 4 ++- .../touchin/roboswag/pagination/Paginator.kt | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/pagination/README.md b/pagination/README.md index 8331b04f..b6bc3d54 100644 --- a/pagination/README.md +++ b/pagination/README.md @@ -11,4 +11,6 @@ View, которая отвечает за отображение постраничного списка. Основной метод - render, который принимает на вход Paginator.State. View состоит из SwipeRefreshLayout и Switcher на 3 состояния: loading, error/empty, success. Success state состоит из RecyclerView, который работает с PaginationAdapter. -### PaginationAdapter +## Дополнительные материалы + +- [Доклад, на котором основан модуль](https://www.youtube.com/watch?v=n9mfLWI8ktE) diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt index 13934448..0eb521c8 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt @@ -8,6 +8,10 @@ import ru.touchin.roboswag.mvi_arch.marker.SideEffect import ru.touchin.roboswag.mvi_arch.marker.StateChange import ru.touchin.roboswag.mvi_arch.marker.ViewState +/** + * Класс, наследник Store, который реализует изменение состояния списка элементов, который поддерживает постраничную загрузку. + * На выход принимает способ обработки ошибки загрузки страницы, метод загрузки страницы и размер страницы. + */ class Paginator( private val errorHandleMod: ErrorHandleMod, private val loadPage: suspend (Int) -> List, @@ -15,35 +19,67 @@ class Paginator( ) : Store(State.Empty) { sealed class Change : StateChange { + // Вызов pull-to-refresh object Refresh : Change() + + // Полная перезагрузка данных при смене внешних параметров: фильтров, сортировок итд object Restart : Change() + + // Пользователь скроллит до конца списка. Вызывает загрузку новой страницы object LoadMore : Change() + + // Пока не понятно object Reset : Change() + + // Загрузка новой страницы прошла успешно data class NewPageLoaded(val pageNumber: Int, val items: List) : Change() + + // Загрузка новой страницы прошла с ошибкой data class PageLoadError(val error: Throwable) : Change() } sealed class Effect : SideEffect { + // Вызов асинхронной загрузки новой страницы data class LoadPage(val page: Int = 0) : Effect() } sealed class State : ViewState { + // Пустой экран object Empty : State() + + // Лоадер в середине экрана object EmptyProgress : State() + + // Ошибка при загрузке первой страницы data class EmptyError(val error: Throwable) : State() + + // Загружен список элементов data class Data(val pageCount: Int = 0, val data: List, val error: Throwable? = null) : State() + + // Показать лоадер при pull-to-refresh data class Refresh(val pageCount: Int, val data: List) : State() + + // Загрузка новой страницы data class NewPageProgress(val pageCount: Int, val data: List) : State() + + // Весь список загружен. Больше нечего грузить data class FullData(val pageCount: Int, val data: List) : State() } sealed class Error { + // Ошибка загрузки новой страницы object NewPageFailed : Error() + + // Ошибка обновления страницы object RefreshFailed : Error() } + // Способы обработки ошибки sealed class ErrorHandleMod { + // Показывать алерт на ошибку data class Alert(val showError: (Error) -> Unit) : ErrorHandleMod() + + // Показывать в конце списка элемент списка с ошибкой object ErrorItem : ErrorHandleMod() } From b87ec19fe24f83dfccb1fdabaa6dbc45cbfa4721 Mon Sep 17 00:00:00 2001 From: Maxim Bachinsky Date: Wed, 16 Sep 2020 19:52:51 +0300 Subject: [PATCH 04/10] added docs for classes of pagination module --- .../main/java/ru/touchin/roboswag/pagination/ErrorItem.kt | 3 +++ .../java/ru/touchin/roboswag/pagination/PaginationAdapter.kt | 5 +++++ .../java/ru/touchin/roboswag/pagination/PaginationView.kt | 5 +++++ .../main/java/ru/touchin/roboswag/pagination/Paginator.kt | 2 +- .../touchin/roboswag/pagination/ProgressAdapterDelegate.kt | 5 ++++- .../main/java/ru/touchin/roboswag/pagination/ProgressItem.kt | 3 +++ 6 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt index cb5db295..097f9254 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt @@ -1,3 +1,6 @@ package ru.touchin.roboswag.pagination +/** + * Элемент списка, который будет показан в конце списка при неудачной загрузке новой страницы + */ object ErrorItem diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt index a2f456ed..f69f1692 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt @@ -6,6 +6,9 @@ import androidx.recyclerview.widget.RecyclerView import ru.touchin.roboswag.recyclerview_adapters.adapters.AdapterDelegate import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter +/** + * Адаптер для отображения постраничного списка. + */ class PaginationAdapter( private val nextPageCallback: () -> Unit, private val itemIdDiff: (old: Any, new: Any) -> Boolean, @@ -26,6 +29,7 @@ class PaginationAdapter( delegate.forEach(this::addDelegate) } + // TODO: перенести в Paginator fun update(data: List, updateState: UpdateState) { submitList(data + listOfNotNull(when (updateState) { is UpdateState.Common -> null @@ -34,6 +38,7 @@ class PaginationAdapter( })) } + // При байндинге одного из последних элементов списка запускается загрузка следующей страницы override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List) { super.onBindViewHolder(holder, position, payloads) if (!fullData && position >= itemCount - 10) nextPageCallback.invoke() diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt index d6d686a6..cff34c30 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt @@ -8,7 +8,11 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager import ru.touchin.extensions.setOnRippleClickListener import ru.touchin.mvi_arch.core_pagination.databinding.ViewPaginationBinding +/** + * Вьюшка, оторая отвечает за отображение списка элементов с постраничной загрузкой + */ // TODO: add an errorview with empty state and error text +// TODO: add LoadingContentView class PaginationView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null @@ -27,6 +31,7 @@ class PaginationView @JvmOverloads constructor( } } + // fun init(refreshCallback: () -> Unit, adapter: PaginationAdapter) { this.refreshCallback = refreshCallback this.adapter = adapter diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt index 0eb521c8..be7e0311 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt @@ -28,7 +28,7 @@ class Paginator( // Пользователь скроллит до конца списка. Вызывает загрузку новой страницы object LoadMore : Change() - // Пока не понятно + // Очищение списка загруженных элементов object Reset : Change() // Загрузка новой страницы прошла успешно diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt index f9b148c3..f29ea26f 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt @@ -2,10 +2,13 @@ package ru.touchin.roboswag.pagination import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate import ru.touchin.mvi_arch.core_pagination.R import ru.touchin.roboswag.components.utils.UiUtils +import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate +/** + * Делегат для отображения лоадера в конце списка + */ class ProgressAdapterDelegate : ItemAdapterDelegate() { override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder = diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt index ffcc5fbf..65f8dcec 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt @@ -1,3 +1,6 @@ package ru.touchin.roboswag.pagination +/** + * Элемент списка, который будет показан в конце списка во время загрузки новой страницы + */ object ProgressItem From 78e9d0a800ccaec1a56d0b4c71b496b94bb9d7c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D1=87?= =?UTF-8?q?=D0=B8=D0=BD=D1=81=D0=BA=D0=B8=D0=B8=CC=86?= Date: Thu, 17 Sep 2020 14:52:41 +0300 Subject: [PATCH 05/10] added full russian docs for pagination's classes --- .../java/ru/touchin/roboswag/pagination/PaginationAdapter.kt | 2 ++ .../main/java/ru/touchin/roboswag/pagination/PaginationView.kt | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt index f69f1692..d136ad1c 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt @@ -22,6 +22,8 @@ class PaginationAdapter( } ) { + // TODO: перенести в список + // Переменная, которая отвечает за отображение лоадера в конце списка. Если переменная равна true, лоадер не будет показан internal var fullData = false init { diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt index cff34c30..110df080 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt @@ -26,12 +26,13 @@ class PaginationView @JvmOverloads constructor( init { with(binding) { swipeToRefresh.setOnRefreshListener { refreshCallback() } + // TODO: удалить и перенести настройку layoutManager в init elementsRecycler.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) emptyText.setOnRippleClickListener { refreshCallback() } } } - // + // Метод, который настраивает view: выставляет adapter для recyclerView и передает лямбду, которую надо вызвать при pull-to-refresh fun init(refreshCallback: () -> Unit, adapter: PaginationAdapter) { this.refreshCallback = refreshCallback this.adapter = adapter From 4bba28aa4855d94d495dc3246579099c33d7c6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D1=87?= =?UTF-8?q?=D0=B8=D0=BD=D1=81=D0=BA=D0=B8=D0=B8=CC=86?= Date: Thu, 17 Sep 2020 20:24:47 +0300 Subject: [PATCH 06/10] wip readme for mvi-arch --- mvi-arch/README.md | 30 +++++++++++++++++-- .../roboswag/mvi_arch/marker/SideEffect.kt | 3 ++ .../roboswag/mvi_arch/marker/StateChange.kt | 4 +++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/mvi-arch/README.md b/mvi-arch/README.md index 6cd5bb4d..efd976f3 100644 --- a/mvi-arch/README.md +++ b/mvi-arch/README.md @@ -1,4 +1,28 @@ -mvi_arch -==== +# mvi_arch + +Модуль для реализации presentation слоя с помощью паттерна mvi. + +Модуль содержит две реализации mvi: упрощенная и полная. + +## Упрощенная реализация через MviViewModel + +### Отличия от стандартного ViewModel + +### ViewModel и AssistedInject + +### MviFragment + +## Полная реализация через MviStoreViewModel + +### Store + +### MviStoreViewModel + +### Почему всегда не использовать MviStoreViewModel + +## Дополнительные материалы + +- [Доклад про MVI от Сергея Рябова]() +- [Доклад про эволюцию презентационных паттернов]() +- [Доклад про расширяемую архитектуру в Lyft]() -TODO: rewrite dependencies diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt index 7fea61c0..a04db3c4 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt @@ -1,3 +1,6 @@ package ru.touchin.roboswag.mvi_arch.marker +/** + * Класс-маркер для вызова асинхронных действий. Например, запрос на сервер или в бд + */ interface SideEffect diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt index 1d5da27a..0ef83501 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt @@ -1,3 +1,7 @@ package ru.touchin.roboswag.mvi_arch.marker +/** + * Класс-маркер для атомарных изменений состояния. Изменения состояния может прийти из view - в ответ на действие пользователя, + * и в результате асинхронных операций - SideEffect. + */ interface StateChange From a6c15eff80d85621c5d3142ac4574cc2de1e155f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D1=87?= =?UTF-8?q?=D0=B8=D0=BD=D1=81=D0=BA=D0=B8=D0=B8=CC=86?= Date: Fri, 18 Sep 2020 19:46:50 +0300 Subject: [PATCH 07/10] added russian readme for mvi-arch --- mvi-arch/README.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/mvi-arch/README.md b/mvi-arch/README.md index efd976f3..f99c1d51 100644 --- a/mvi-arch/README.md +++ b/mvi-arch/README.md @@ -8,21 +8,41 @@ ### Отличия от стандартного ViewModel -### ViewModel и AssistedInject +У MviViewModel есть один абстрактный метод для обработки внешних событий пользователя. +События должны наследоваться от интерфейса-маркера ViewAction. Также у MviViewModel есть +livedata, которая держит в себе single state экрана. То есть у MviViewModel есть один вход и один выход. + +### MviViewModel и AssistedInject + +Помимо ограничений в количестве потока данных MviViewModel использует AssistedInject в конструктор для +получения inititalState через конструктор и механизм для сохранения данных экрана в Bundle - SavedStateHandle. +AssistedInject используется для передачи SavedStateHandle из фрагмента, который подключает ViewModel. ### MviFragment +Главная особенность MviFragment - связь с MviViewModel. Получить экземпляр класса MviViewModel можно через +lazy делегата - viewModel(). + ## Полная реализация через MviStoreViewModel ### Store +Store - реализация стора из Redux. Он держит в себе стейт машину, у которой могут быть SideEffect. Сайд эффекты обрабатываются отдельно +в одном kotlin flow. + ### MviStoreViewModel +MviStoreViewModel наследуется от MviViewModel. Единственная ценность этого класса - он хранит в себе список Store, передает Action внутрь Store +комбинирует стейты всех сторов в ожин большой стейт. + ### Почему всегда не использовать MviStoreViewModel +Реализация полного MVI требует много кода. Для простых экранов, то есть для 85 процентов экранов рекомендуется использовать MviViewModel. + ## Дополнительные материалы -- [Доклад про MVI от Сергея Рябова]() -- [Доклад про эволюцию презентационных паттернов]() -- [Доклад про расширяемую архитектуру в Lyft]() +- [Доклад про MVI от Сергея Рябова](https://youtu.be/hBkQkjWnAjg) +- [Доклад про эволюцию презентационных паттернов](https://youtu.be/J0YPKcDKumk) +- [Доклад про расширяемую архитектуру в Lyft](https://www.youtube.com/watch?v=_cFHtjIWjCc) +- [Презентация доклада со внутреннего митапа](Доклад Илюхи) From 2d70c6884e351d5ee355c7eb144456cdde0eb854 Mon Sep 17 00:00:00 2001 From: rybakovi Date: Thu, 1 Oct 2020 18:29:00 +0300 Subject: [PATCH 08/10] add description --- .../java/ru/touchin/roboswag/mvi_arch/core/Store.kt | 9 +++++++++ .../roboswag/mvi_arch/di/ViewModelAssistedFactory.kt | 7 +++++++ .../touchin/roboswag/mvi_arch/di/ViewModelFactory.kt | 4 ++++ .../ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt | 6 ++++-- .../ru/touchin/roboswag/mvi_arch/marker/StateChange.kt | 9 +++++++-- .../roboswag/navigation_cicerone/CiceroneTuner.kt | 10 ++++++++++ .../navigation_cicerone/flow/FlowNavigation.kt | 4 ++++ .../navigation_cicerone/flow/FlowNavigationModule.kt | 6 ++++++ .../java/ru/touchin/roboswag/pagination/ErrorItem.kt | 3 ++- .../touchin/roboswag/pagination/PaginationAdapter.kt | 7 ++++++- .../ru/touchin/roboswag/pagination/PaginationView.kt | 3 ++- .../java/ru/touchin/roboswag/pagination/Paginator.kt | 8 ++++++-- .../roboswag/pagination/ProgressAdapterDelegate.kt | 3 ++- .../ru/touchin/roboswag/pagination/ProgressItem.kt | 3 ++- 14 files changed, 71 insertions(+), 11 deletions(-) diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/Store.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/Store.kt index 56eef649..5801b989 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/Store.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/core/Store.kt @@ -18,6 +18,15 @@ import ru.touchin.roboswag.mvi_arch.marker.SideEffect import ru.touchin.roboswag.mvi_arch.marker.StateChange import ru.touchin.roboswag.mvi_arch.marker.ViewState +/** + * Base [Store] to use in [MviStoreViewModel]. + * + * You should implement it: + * 1) define [StateChange], [SideEffect] and [ViewState] - usually sealed class with objects and data classes. + * 2) override method [reduce] - it should transform current state ViewState and StateChange to pair of + * + **/ + abstract class Store( initialState: State ) { diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelAssistedFactory.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelAssistedFactory.kt index d958ce08..9c01f7cb 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelAssistedFactory.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelAssistedFactory.kt @@ -3,6 +3,13 @@ package ru.touchin.roboswag.mvi_arch.di import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +/** + * For transmission SavedStateHandle of viewModel's fragment. + * + * ViewModel should have inner interface, that implements ViewModelAssistedFactory + * You should use @AssistedInject in viewModel's constructor and @Assisted in argument to assist. + */ + interface ViewModelAssistedFactory { fun create(handle: SavedStateHandle): VM } diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelFactory.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelFactory.kt index 5ae4d27e..f040ffa0 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelFactory.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/di/ViewModelFactory.kt @@ -6,6 +6,10 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.savedstate.SavedStateRegistryOwner +/** + * Factory that will be used by [ViewModelProvider] to instantiate viewModel in [MviFragment]. + * + */ class ViewModelFactory( private val viewModelMap: MutableMap, ViewModelAssistedFactory>, owner: SavedStateRegistryOwner, diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt index a04db3c4..7ef2db36 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/SideEffect.kt @@ -1,6 +1,8 @@ package ru.touchin.roboswag.mvi_arch.marker /** - * Класс-маркер для вызова асинхронных действий. Например, запрос на сервер или в бд - */ + * Class-marker to invoke asynchronous actions such as API call or database query. + * Side effects create in [Store.reduce]. + * + * */ interface SideEffect diff --git a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt index 0ef83501..a5021e5d 100644 --- a/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt +++ b/mvi-arch/src/main/java/ru/touchin/roboswag/mvi_arch/marker/StateChange.kt @@ -1,7 +1,12 @@ package ru.touchin.roboswag.mvi_arch.marker /** - * Класс-маркер для атомарных изменений состояния. Изменения состояния может прийти из view - в ответ на действие пользователя, - * и в результате асинхронных операций - SideEffect. + * This interface should be implemented to create your own state change and use it with [MviFragment] and [MviStoreViewModel]. + * + * Class-marker for atomic state change. State change can come from view (in response to user's action) + * or as a SideEffect's result. + * + * StateChange affects current state: in [Store.reduce] pair creates new ViewState and list of SideEffects. */ + interface StateChange diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/CiceroneTuner.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/CiceroneTuner.kt index ba525d38..099676ab 100644 --- a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/CiceroneTuner.kt +++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/CiceroneTuner.kt @@ -6,6 +6,16 @@ import androidx.lifecycle.OnLifecycleEvent import ru.terrakok.cicerone.Navigator import ru.terrakok.cicerone.NavigatorHolder +/** + * CiceroneTuner is responsible for adding Navigator to NavigatorHolder in onResume and + * removing Navigator in onPause. + * + * You should add CiceroneTuner like an Observer of SingleActivity or FlowFragment lifecycle. + * + * @param navigatorHolder - interface to connect a {@link Navigator} to the {@link Cicerone}; + * @param navigator - {@link Navigator} implementation; + */ + class CiceroneTuner( private val navigatorHolder: NavigatorHolder, private val navigator: Navigator diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigation.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigation.kt index fa67fc74..d23e0430 100644 --- a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigation.kt +++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigation.kt @@ -2,5 +2,9 @@ package ru.touchin.roboswag.navigation_cicerone.flow import javax.inject.Qualifier +/** + * Qualifier to designate Router for navigation between features + */ + @Qualifier annotation class FlowNavigation diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigationModule.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigationModule.kt index 25925630..59970809 100644 --- a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigationModule.kt +++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowNavigationModule.kt @@ -7,6 +7,12 @@ import ru.terrakok.cicerone.NavigatorHolder import ru.terrakok.cicerone.Router import ru.touchin.roboswag.navigation_base.scopes.FeatureScope +/** + * Module to provide Cicerone. + * + * You should add it to @Component annotation of your feature's component. + */ + @Module class FlowNavigationModule { diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt index 097f9254..b6a1fc47 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/ErrorItem.kt @@ -1,6 +1,7 @@ package ru.touchin.roboswag.pagination /** - * Элемент списка, который будет показан в конце списка при неудачной загрузке новой страницы + * Element to display in the end of list if an error occurred while loading new page */ + object ErrorItem diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt index d136ad1c..8d41986d 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt @@ -7,7 +7,12 @@ import ru.touchin.roboswag.recyclerview_adapters.adapters.AdapterDelegate import ru.touchin.roboswag.recyclerview_adapters.adapters.DelegationListAdapter /** - * Адаптер для отображения постраничного списка. + * Adapter for showing [Paginator]. + * + * @param nextPageCallback - callback to load data for next page (if not all data loaded); + * @param itemIdDiff - compares whether two elements are equal; + * @param delegate - list of delegates to add to adapter of RecyclerView. + * */ class PaginationAdapter( private val nextPageCallback: () -> Unit, diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt index 110df080..c3efa434 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt @@ -9,8 +9,9 @@ import ru.touchin.extensions.setOnRippleClickListener import ru.touchin.mvi_arch.core_pagination.databinding.ViewPaginationBinding /** - * Вьюшка, оторая отвечает за отображение списка элементов с постраничной загрузкой + * View, responsible for displaying paginator */ + // TODO: add an errorview with empty state and error text // TODO: add LoadingContentView class PaginationView @JvmOverloads constructor( diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt index be7e0311..fed4d327 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt @@ -9,9 +9,13 @@ import ru.touchin.roboswag.mvi_arch.marker.StateChange import ru.touchin.roboswag.mvi_arch.marker.ViewState /** - * Класс, наследник Store, который реализует изменение состояния списка элементов, который поддерживает постраничную загрузку. - * На выход принимает способ обработки ошибки загрузки страницы, метод загрузки страницы и размер страницы. + * Class for state changing of list, support page-loading, implements [Store] + * + * @param errorHandleMod - error handling method (show Alert or ErrorItem); + * @param loadPage - method for loading data; + * @param pageSize - size of one page */ + class Paginator( private val errorHandleMod: ErrorHandleMod, private val loadPage: suspend (Int) -> List, diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt index f29ea26f..37eb6a8d 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressAdapterDelegate.kt @@ -7,8 +7,9 @@ import ru.touchin.roboswag.components.utils.UiUtils import ru.touchin.roboswag.recyclerview_adapters.adapters.ItemAdapterDelegate /** - * Делегат для отображения лоадера в конце списка + * Delegate for displaying loader in the end of list */ + class ProgressAdapterDelegate : ItemAdapterDelegate() { override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder = diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt index 65f8dcec..0bdcc856 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/ProgressItem.kt @@ -1,6 +1,7 @@ package ru.touchin.roboswag.pagination /** - * Элемент списка, который будет показан в конце списка во время загрузки новой страницы + * Element to display in the end of list while loading new page */ + object ProgressItem From d2bdac1f4f1105519fd50f9fd71fcb79e6d66e9d Mon Sep 17 00:00:00 2001 From: rybakovi Date: Fri, 2 Oct 2020 16:21:54 +0300 Subject: [PATCH 09/10] fix comments --- mvi-arch/README.md | 17 +++++++++-------- navigation-cicerone/README.md | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/mvi-arch/README.md b/mvi-arch/README.md index f99c1d51..b363db99 100644 --- a/mvi-arch/README.md +++ b/mvi-arch/README.md @@ -8,15 +8,16 @@ ### Отличия от стандартного ViewModel -У MviViewModel есть один абстрактный метод для обработки внешних событий пользователя. +У MviViewModel есть один метод для обработки внешних событий пользователя - dispatchAction. События должны наследоваться от интерфейса-маркера ViewAction. Также у MviViewModel есть -livedata, которая держит в себе single state экрана. То есть у MviViewModel есть один вход и один выход. +livedata, содржащую в себе single state экрана, который наследуется от интерфейса-маркера ViewState. +То есть у MviViewModel есть один вход и один выход. ### MviViewModel и AssistedInject -Помимо ограничений в количестве потока данных MviViewModel использует AssistedInject в конструктор для -получения inititalState через конструктор и механизм для сохранения данных экрана в Bundle - SavedStateHandle. -AssistedInject используется для передачи SavedStateHandle из фрагмента, который подключает ViewModel. +MviViewModel использует AssistedInject в конструктор для получения inititalState через конструктор +и механизм для сохранения данных экрана в Bundle - SavedStateHandle. +AssistedInject используется для передачи navArgs из фрагмента, который подключает ViewModel. ### MviFragment @@ -32,8 +33,8 @@ Store - реализация стора из Redux. Он держит в себ ### MviStoreViewModel -MviStoreViewModel наследуется от MviViewModel. Единственная ценность этого класса - он хранит в себе список Store, передает Action внутрь Store -комбинирует стейты всех сторов в ожин большой стейт. +MviStoreViewModel наследуется от MviViewModel. Единственная ценность этого класса - он хранит в себе список Store, передает Action внутрь Store, +комбинирует стейты всех сторов в один большой стейт. ### Почему всегда не использовать MviStoreViewModel @@ -44,5 +45,5 @@ MviStoreViewModel наследуется от MviViewModel. Единственн - [Доклад про MVI от Сергея Рябова](https://youtu.be/hBkQkjWnAjg) - [Доклад про эволюцию презентационных паттернов](https://youtu.be/J0YPKcDKumk) - [Доклад про расширяемую архитектуру в Lyft](https://www.youtube.com/watch?v=_cFHtjIWjCc) -- [Презентация доклада со внутреннего митапа](Доклад Илюхи) +- [Презентация доклада со внутреннего митапа](https://docs.google.com/presentation/d/1gBg8n8xAyIytDo1-L9GvrCkrM6AFynLPYKcsQ_NPs7k/edit#slide=id.p) diff --git a/navigation-cicerone/README.md b/navigation-cicerone/README.md index a7bc2762..c0b6c5fc 100644 --- a/navigation-cicerone/README.md +++ b/navigation-cicerone/README.md @@ -9,7 +9,7 @@ Для использования Single-Activity подхода возникают ситуации, когда несколько экранов нужно объединить одной родительской сущностью. Объединение необходимо для хранения общего dagger компонента на flow или организации собственной навигации. Раньше можно было использовать Activity. Но в Single-Activity должно быть только одно активити на приложение. В такой архитектуре можно использовать -parent fragment для всех экран - FlowFragment. FlowFragment обладает своей навигацией. Для навигации используется childFragmentManager этого фрагмента. +parent fragment для всех экранов - FlowFragment. FlowFragment обладает своей навигацией. Для навигации используется childFragmentManager этого фрагмента. Чтобы использовать эту навигацию в di, в модуль добавлены dagger-модуль FlowNavigationModule, который держит основные cicerone сущности, и именная аннотация FlowNavigation. From 4f9fa15e6db0f41f1cf78e5ef7753db14735cfe4 Mon Sep 17 00:00:00 2001 From: rybakovi Date: Thu, 8 Oct 2020 16:56:11 +0300 Subject: [PATCH 10/10] fix comments and translate to english --- mvi-arch/README.md | 2 +- .../navigation_cicerone/flow/FlowFragment.kt | 2 +- .../roboswag/pagination/PaginationAdapter.kt | 11 ++--- .../roboswag/pagination/PaginationView.kt | 6 +-- .../touchin/roboswag/pagination/Paginator.kt | 46 +++++++++---------- 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/mvi-arch/README.md b/mvi-arch/README.md index b363db99..17257b5b 100644 --- a/mvi-arch/README.md +++ b/mvi-arch/README.md @@ -38,7 +38,7 @@ MviStoreViewModel наследуется от MviViewModel. Единственн ### Почему всегда не использовать MviStoreViewModel -Реализация полного MVI требует много кода. Для простых экранов, то есть для 85 процентов экранов рекомендуется использовать MviViewModel. +Реализация полного MVI требует много кода. Для простых экранов, (т.е. для большинства экранов) рекомендуется использовать MviViewModel. ## Дополнительные материалы diff --git a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt index 4d2ed53b..eae60ad5 100644 --- a/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt +++ b/navigation-cicerone/src/main/java/ru/touchin/roboswag/navigation_cicerone/flow/FlowFragment.kt @@ -15,7 +15,7 @@ import ru.touchin.roboswag.navigation_cicerone.CiceroneTuner import javax.inject.Inject /** - * Base parent fragment for fragments of hole feature. FlowFragment has own navigator based on childFragmentManager. + * Base parent fragment for fragments of whole feature. FlowFragment has own navigator based on childFragmentManager. * FlowFragment is responsible for handling of back button press. * * You should connect FlowNavigationModule to your Dagger component and add inject method for your flow fragment. diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt index 8d41986d..6054e3fb 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationAdapter.kt @@ -27,16 +27,15 @@ class PaginationAdapter( } ) { - // TODO: перенести в список - // Переменная, которая отвечает за отображение лоадера в конце списка. Если переменная равна true, лоадер не будет показан - internal var fullData = false + // TODO: transfer to list + internal var isLoaderInTheEndInvisible = false init { addDelegate(ProgressAdapterDelegate()) delegate.forEach(this::addDelegate) } - // TODO: перенести в Paginator + // TODO: transfer to Paginator fun update(data: List, updateState: UpdateState) { submitList(data + listOfNotNull(when (updateState) { is UpdateState.Common -> null @@ -45,10 +44,10 @@ class PaginationAdapter( })) } - // При байндинге одного из последних элементов списка запускается загрузка следующей страницы + // While binding one of the last elements of the list loading of the next page starts override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List) { super.onBindViewHolder(holder, position, payloads) - if (!fullData && position >= itemCount - 10) nextPageCallback.invoke() + if (!isLoaderInTheEndInvisible && position >= itemCount - 10) nextPageCallback.invoke() } sealed class UpdateState { diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt index c3efa434..5fd04968 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/PaginationView.kt @@ -27,13 +27,13 @@ class PaginationView @JvmOverloads constructor( init { with(binding) { swipeToRefresh.setOnRefreshListener { refreshCallback() } - // TODO: удалить и перенести настройку layoutManager в init + // TODO: delete and transfer the layoutManager setting to init elementsRecycler.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL) emptyText.setOnRippleClickListener { refreshCallback() } } } - // Метод, который настраивает view: выставляет adapter для recyclerView и передает лямбду, которую надо вызвать при pull-to-refresh + // Method for setting View: sets adapter for recyclerView and passes lambda to call while pull-to-refresh fun init(refreshCallback: () -> Unit, adapter: PaginationAdapter) { this.refreshCallback = refreshCallback this.adapter = adapter @@ -51,7 +51,7 @@ class PaginationView @JvmOverloads constructor( else -> elementsRecycler.id } ) - adapter.fullData = state === Paginator.State.Empty || state is Paginator.State.FullData<*> + adapter.isLoaderInTheEndInvisible = state === Paginator.State.Empty || state is Paginator.State.FullData<*> when (state) { is Paginator.State.EmptyError, Paginator.State.Empty, Paginator.State.EmptyProgress -> { diff --git a/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt b/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt index fed4d327..7997d64c 100644 --- a/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt +++ b/pagination/src/main/java/ru/touchin/roboswag/pagination/Paginator.kt @@ -23,67 +23,67 @@ class Paginator( ) : Store(State.Empty) { sealed class Change : StateChange { - // Вызов pull-to-refresh + // call pull-to-refresh object Refresh : Change() - // Полная перезагрузка данных при смене внешних параметров: фильтров, сортировок итд + // Full reloading data when changing external parameters: filters, sorts, etc. object Restart : Change() - // Пользователь скроллит до конца списка. Вызывает загрузку новой страницы + // User scrolls to the end of the list. Calls loading of new page object LoadMore : Change() - // Очищение списка загруженных элементов + // Clearing the list and loaded elements object Reset : Change() - // Загрузка новой страницы прошла успешно + // Loading the new page was successful data class NewPageLoaded(val pageNumber: Int, val items: List) : Change() - // Загрузка новой страницы прошла с ошибкой + // Loading the new page ended with error data class PageLoadError(val error: Throwable) : Change() } sealed class Effect : SideEffect { - // Вызов асинхронной загрузки новой страницы + // Call asynchronous load of a new page data class LoadPage(val page: Int = 0) : Effect() } sealed class State : ViewState { - // Пустой экран + // Empty screen object Empty : State() - // Лоадер в середине экрана + // Loader in the middle of screen object EmptyProgress : State() - // Ошибка при загрузке первой страницы + // Error while loading first page data class EmptyError(val error: Throwable) : State() - // Загружен список элементов + // Loaded list of elements data class Data(val pageCount: Int = 0, val data: List, val error: Throwable? = null) : State() - // Показать лоадер при pull-to-refresh + // Show loader on pull-to-refresh data class Refresh(val pageCount: Int, val data: List) : State() - // Загрузка новой страницы + // Loading new page data class NewPageProgress(val pageCount: Int, val data: List) : State() - // Весь список загружен. Больше нечего грузить + // The whole list is loaded. Nothing to load more. data class FullData(val pageCount: Int, val data: List) : State() } sealed class Error { - // Ошибка загрузки новой страницы - object NewPageFailed : Error() - // Ошибка обновления страницы - object RefreshFailed : Error() + object NewPageLoadingFailed : Error() + + object RefreshPageFailed : Error() + } - // Способы обработки ошибки + // How to react to an error sealed class ErrorHandleMod { - // Показывать алерт на ошибку + // Show alert for error data class Alert(val showError: (Error) -> Unit) : ErrorHandleMod() - // Показывать в конце списка элемент списка с ошибкой + // Show in the end of list an element of list with error object ErrorItem : ErrorHandleMod() } @@ -145,7 +145,7 @@ class Paginator( is State.Refresh<*> -> { when (errorHandleMod) { is ErrorHandleMod.Alert -> { - errorHandleMod.showError(Error.RefreshFailed) + errorHandleMod.showError(Error.RefreshPageFailed) State.Data(currentState.pageCount, currentState.data) } is ErrorHandleMod.ErrorItem -> { @@ -160,7 +160,7 @@ class Paginator( is State.NewPageProgress<*> -> { when (errorHandleMod) { is ErrorHandleMod.Alert -> { - errorHandleMod.showError(Error.NewPageFailed) + errorHandleMod.showError(Error.NewPageLoadingFailed) State.Data(currentState.pageCount, currentState.data) } is ErrorHandleMod.ErrorItem -> {