Навигация внутри Android приложения

в 16:17, , рубрики: android, kotlin, mvp, mvvm, navigation, Разработка под android

Введение

При Андроид разработке мы используем разные архитектурные решения(паттерны). Например 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.

Так как не хотел чтобы в статье было много кода я ограничусь этим примером. Вот ссылка на репозиторий для подробного изучения библиотеки.

Автор: hakandr

Источник

* - обязательные к заполнению поля