Введение
При Андроид разработке мы используем разные архитектурные решения(паттерны). Например Mvp, Mvvm, Mvi и т.д… Каждый из этих паттернов решает несколько важных задач и поскольку они не идеальны они нам оставляют кое-какие нерешенные задачи. К примеру этих задач относятся навигация внутри приложения(routing), передача информации с экрана на экран(говоря экран я имею ввиду Activity, Fragment или View), Сохранение состояний приложения при смене конфигурации(configuration change).
В нашей компании мы тоже столкнулись с этими задачами, кое-какие были решены легким путем но первый из них так и не нашел конкретное решение, перепробовав различные методы его решения мы написали свою библиотеку Flowzard.
Задача
В нашей компании мы используем Mvp архитектуру. Чтобы иметь максимальную гибкость при показе, смене и передачи данных между экранами мы стараемся следовать принципу называемому Single-responsibility principle. Принцип гласит о том что каждый модуль должен решать конкретную задачу. В нашем случае экран должен быть изолирован от глобальной задачи и должен решать свою конкретную задачу показывать/принимать информацию. Он не должен знать о других экранах вообще. Так мы можем достичь максимальной гибкости. Ниже пример настройки и использования библиотеки.
Flowzard
Создание flow
class MainFlow(flowManager: FlowManager) : Flow(flowManager) { // Вызывается при создании или восстановлении flow override fun onCreate(savedInstance: DataBunch?, data: DataBunch?) { super.onCreate(savedInstance, data) } }
Создание flow navigator
Навигаторы выполняют две функции: Создают flow контейнеры(Activity, Fragment, View) для переходов между flow и экраны для переходов внутри flow.
class DefaultFlowNavigator(activity: AppCompatActivity) : SimpleFlowNavigator(activity){ // Вызывается при связывании flow с Activity override fun getActivityIntent(id: String, data: Any?): Intent { return when (id) { Flows.SIGN_UP -> Intent(activity, SignupActivity::class.java) else -> throw RuntimeException("Cannot find activity for id=$id") } } }
Привязывание к Activity
Чтобы связать Activity с Flow мы наследуем FlowActivity и предоставляем Navigator, в нашем случае DefaultFlowNavigator.
class MainActivity : FlowActivity() { override val navigator: Navigator get() = DefaultFlowNavigator(this) }
Создание FlowManager
class DefaultFlowManager : FlowManager() { // Вызывается при создании главного(main) flow override fun createMainFlow(): Flow { return MainFlow(this) } // Вызывается при создании flow override fun createFlow(id: String): Flow { return when (id) { Flows.SIGN_UP -> SignupFlow(this) else -> throw RuntimeException("Cannot find flow for id=$id") } } } // Привязываем наш FlowManager к Application class App : Application(), FlowManagerProvider { private val flowManager = DefaultFlowManager() override fun getProvideManager(): FlowManager { return flowManager } }
Передача сообщений между flow и экраном
При нажатии кнопки login активити отправляет сообщение в main flow. Flow создает SIGN_UP flow и ожидает ответа от него. При удачном логине SIGN_UP flow отправляет результат в main flow и вызывается onFlowResult:MainFlow с кодом и объектом результата. Main flow проверяет, если результат правильный то отправляет сообщение обратно в активити, что пользователь удачно залогинился.
class MainFlow(flowManager: FlowManager) : Flow(flowManager) { companion object { const val LOGIN_REQUEST_CODE = 1 } // вызывается при получении сообщений override fun onMessage(code: String, message: Any) { super.onMessage(code, message) if (code == "main" && message == "login") { newFlow(Flows.SIGN_UP, LOGIN_REQUEST_CODE) } } // вызывается при получении результата от другого flow override fun onFlowResult(requestCode: Int, result: Result) { super.onFlowResult(requestCode, result) if (requestCode == LOGIN_REQUEST_CODE && result is Result.SUCCESS) { sendMessageFromFlow("main", true) } } } class MainActivity : FlowActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) loginButton.setOnClickListener { // отправляет сообщение “login” с кодом “main” flow.sendMessage("main", "login") } // слушает сообщение с кодом “main” setMessageListener("main") { if (it is Boolean && it) { statusTextView.text = "Logined" } } } }
Сохранение состоянии при смене конфигурации или при остановке процесса операционной системой
Так как Андроид сохраняет стеки Activity и Fragment то созданные flow с этими контейнерами будут сохранять и восстанавливать свое состояние. С View контейнером нужно будет писать свой кастомный FlowManager так как библиотека пока еще не имеет такой менеджер. В следующем обновлении будет фича для сохранении промежуточных данных из flow.
Так как не хотел чтобы в статье было много кода я ограничусь этим примером. Вот ссылка на репозиторий для подробного изучения библиотеки.
