Navigation inside Android application

    Introduction


    When Android is developed, we use different architectural solutions (patterns). For example, Mvp , Mvvm , Mvi , etc ... Each of these patterns solves several important problems, and since they are not perfect, they leave us some unsolved problems. For example, these tasks include navigation within the application (routing), transferring information from the screen to the screen (speaking the screen, I mean Activity, Fragment or View), Saving the application state when changing configuration (configuration change).


    In our company, we also faced these problems, some were solved in an easy way, but the first of them did not find a specific solution, having tried various methods of its solution, we wrote our own Flowzard library .


    Task


    In our company we use Mvp architecture. In order to have maximum flexibility in showing, changing and transferring data between screens, we try to follow the principle called Single-responsibility principle . The principle says that each module should solve a specific task. In our case, the screen should be isolated from the global task and should solve its specific task to show / receive information. He does not need to know about other screens at all. So we can achieve maximum flexibility. Below is an example of setting up and using the library.


    Flowzard


    Creating flow


    classMainFlow(flowManager: FlowManager) : Flow(flowManager) {
        // Вызывается при создании или восстановлении flow overridefunonCreate(savedInstance: DataBunch?, data: DataBunch?) {
            super.onCreate(savedInstance, data)
        }
    }

    Creating a flow navigator


    Navigators perform two functions: Create flow containers (Activity, Fragment, View) for transitions between flow and screens for transitions within flow.


    classDefaultFlowNavigator(activity: AppCompatActivity) : SimpleFlowNavigator(activity){
        // Вызывается при связывании flow с Activity overridefungetActivityIntent(id: String, data: Any?): Intent {
            returnwhen (id) {
                Flows.SIGN_UP -> Intent(activity, SignupActivity::class.java)else -> throw RuntimeException("Cannot find activity for id=$id")
            }
        }
    }

    Tying to an Activity


    To associate an Activity with Flow, we inherit FlowActivity and provide a Navigator, in our case DefaultFlowNavigator.


    classMainActivity : FlowActivity() {
        overrideval navigator: Navigator
            get() = DefaultFlowNavigator(this)
    }

    Creating FlowManager


    classDefaultFlowManager : FlowManager() {
        // Вызывается при создании главного(main) flowoverridefuncreateMainFlow(): Flow {
            return MainFlow(this)
        }
        // Вызывается при создании flowoverridefuncreateFlow(id: String): Flow {
            returnwhen (id) {
                Flows.SIGN_UP -> SignupFlow(this)
                else -> throw RuntimeException("Cannot find flow for id=$id")
            }
        }
    }
    // Привязываем наш FlowManager к ApplicationclassApp : Application(), FlowManagerProvider {
        privateval flowManager = DefaultFlowManager()
        overridefungetProvideManager(): FlowManager {
            return flowManager
        }
    }

    Messaging between flow and screen


    When you press the login button, the activation sends a message to main flow. Flow creates a SIGN_UP flow and waits for a response from it. With a successful login, SIGN_UP flow sends the result to the main flow and calls onFlowResult: MainFlow with the code and the result object. Main flow checks if the result is correct then sends a message back to the activation that the user has successfully logged in.


    classMainFlow(flowManager: FlowManager) : Flow(flowManager) {
        companionobject {
            constval LOGIN_REQUEST_CODE = 1
        }
        // вызывается при получении сообщенийoverridefunonMessage(code: String, message: Any) {
            super.onMessage(code, message)
            if (code == "main" && message == "login") {
                newFlow(Flows.SIGN_UP, LOGIN_REQUEST_CODE)
            }
        }
        // вызывается при получении результата от другого flowoverridefunonFlowResult(requestCode: Int, result: Result) {
            super.onFlowResult(requestCode, result)
            if (requestCode == LOGIN_REQUEST_CODE && result is Result.SUCCESS) {
                sendMessageFromFlow("main", true)
            }
        }
    }
    classMainActivity : FlowActivity() {
        overridefunonCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            loginButton.setOnClickListener {
                // отправляет сообщение “login” с кодом “main” 
                flow.sendMessage("main", "login")
            }
            // слушает сообщение с кодом “main”
            setMessageListener("main") {
                if (it isBoolean && it) {
                    statusTextView.text = "Logined"
                }
            }
        }
    }

    Saving state when changing configuration or when the operating system stops the process


    Since Android saves the Activity and Fragment stacks, the created flows with these containers will save and restore their state. With the View container, you will need to write your custom FlowManager as the library does not yet have such a manager. In the next update there will be a feature for saving intermediate data from flow.


    Since I didn’t want a lot of code in the article, I will limit myself to this example. Here is a link to the repository for a detailed study of the library.


    Also popular now: