Better Navigation with State Machines

Tram Ho

Introduction

The in-app navigation has a nature like a pet. It seems harmless and underestimated, but if you don’t isolate it properly, it will soon leave its ugliness behind in the whole code. At the beginning of the year, I was involved in a project for a food delivery application. I was not involved in the design or development of the app, but was involved in support for the work. That means I was able to look at the codebase with the advantage of both rethinking and external perspectives, giving me a double advantage in becoming truly understanding. And navigating in my opinion is a mess.

To be clear, I’m not trying to ridicule any of my colleagues, React Navtive has gone through a lot of changes in its development, and people still find the problems; a unified navigation concept seems to be the last thing on everyone’s mind. However, there is always value in looking back at how a certain job could be better handled.

So, consider how you get over this. Also note that this paradigm is meaningless for React Native; It is a common model that can be applied to any application that uses screen-based navigation.

The Problem

The instances of our application have several well-defined screen streams. When the user needs to place an order, they start with the seller selection screen, then move to the seller listing screen, the order confirmation screen, and finally the order confirmation screen. success. The login or sign up process also has its own thread to perform the correct process of modifying functions.

 

The first part is the screens that can be part of many different threads. For example, a user can save a new shipping address from both the settings screens and the order confirmation screens. The app also has a list of the dishes the user has currently ordered, along with a button that takes them directly to the supplier’s menu, with the dishes already in the cart, they can be are easily removed in the middle of the order flow.

 

Typically, all of this is handled by their monitors. if a screen is part of many different threads, or if you need to start it in the middle of any thread, you have to add it to all branches and prepare the data first and then. You can separate the source code into a view class and a logic class, but in the end it’s still the screen’s source code.

There is also data persistence, because during thread execution, each screen contributes a detailed condition that needs to be saved for reuse. In the order flow, the user will first choose the supplier, then the dishes, then the shipping address, until all information is repackaged and sent to the server via an API. Until that point, these small pieces need to be collected somewhere.

You can try to circle a single object from first screen to next (Keep in mind that different streams will collect different data, so or happily visualize about that work in the manuscript); or if the application uses Redux, you can push everything to a storage center and make it accessible from all screens (Creating global variables is a must).

In the end, you’ve got all the computed stuff, and here’s one final blast: Make a version of the application that can be licensed to a single vendor. When the user initiates an ordering process, the vendor selection screen is ignored, and they are replaced directly by the dish selection screen. So, basically, you need to change the behavior for each interaction that initiates a new order flow. There’s one on the home screen, one in the hamburger menu, plus the initiation of an automated ordering process when a user registers, and … that’s all. Maybe. It will come during testing.

Try something different.

The Concept

By definition, a state machine is:

A master device can be a set number of steady-state conditions depending on the previous condition and the current values ​​of the input values.

In particular, this means that a female object responds to the call to any method managed by a local variable (called state), whose value is limited by a The setting is clearly defined. A common example might be a media playback object, its states can be mapped to a flowchat like below:

 

A particular method called an object is valid only in specific states. The process of calling play () in any sate other than Stopped , or setSource () in any other Created , can either silently error, or raise an exception. The effect method called will usually change the state of the object.

Specifically, the methods of a state machine object will follow the pattern below:

  1. Validation : Determines if the called method is valid in the object’s current state, otherwise, return or raise an exception.
  2. Execution : Execute whatever side effect is needed.
  3. Transition : Change the state of the object.
  4. Result (optional) : Returns the result of any one activity.

State machines can be deployed in a variety of ways. The most basic solution is to define an enum with the possible states of the object, and use an element of that enum type:

Depending on the language you are using, you might also be able to use function pointers or a local class to encapsulate more specific behavioral states. The rollout is really unimportant right now.

The Application

The basic idea is to add a conditional layer between screens and navigation objects, with the goal of encapsulating all the logic for the current thread. Instead of each screen specifying which one will be displayed next independently, they indicate the signal to the stream object, optionally passing some data as their result, such as collecting input data from the user. It is a flow object that examines this data, determines what is needed to do next, and moves to an appropriate screen.

 

State machines are suitable for us to easily handle this problem, the machine here is the flow object, and its current state is the current screen. When a new screen is displayed, the flow object passes it a callback; The screen can use this callback when its purpose has been accomplished. Inside, the callback method will follow a basic pattern similar to the one above:

  1. Validation: check the data received from the screen.
  2. Execution: securing relevant data for local storage of the flow object, or performing some sort of non-direct operation that easily forces its own screen.
  3. Transition: Move to the new screen, pass callback functions and any other data conditions that may be required during its own initialization.

For example, here is how you can write callback functions that handle user selections from the supplier list (plus some conditional functions) in the order flow:

Once screens are tied to each other in flows, the logical abstraction can proceed to a higher level. Like how screens can signal the flow that a new screen should load, flows can also signal to the application that another flow should be initiated. More specifically, the application’s flows can also be organized in such a state machine:

When the application is launched, it loads the authentication thread if the user has not logged in successfully, and the default thread (which only exists on the home screen) if present. When the user clicks the “New Order” button on the home screen, the default flow can signal the application to start the order flow, and so on.

The Pros and Cons

The best advantage is that the business logic for the application is accurate and completely separate. Its source code can become more complex, but the application logic is very simple. If you want to understand which screen will come next, and under what conditions, you just need to look at this single place.

This logical focus also makes it adaptable to change. As in our example, if you need to create a sub-branch of the application where the order flow starts on another screen, you only need to change a few lines in the source code of the order flow, and nowhere else. The creation of new threads can also be performed without having to change any of the screen elements.

Having a single object that exists throughout the life of the flow also makes it an ideal candidate for data conservation. Each time a screen returns a result, the flow can choose to store it, then it can transfer all or part of the stored data to a new screen or a new flow.

On the downside, adding a new layer to any architecture inevitably brings a ton of costs, as well as making the code more complex to grasp the whole thing. However, we find that using state machines for navigation has more advantages than added complexity.

Source

https://itnext.io/better-navigation-with-state-machines-6c917866f351

References

The Rise Of The State Machines Architecture Components: Easy Mapping of Actions and UI State Discovering Event-Driven Architecture for Android A View State Machine for Network Calls on Android Finite State Machines + Android + Kotlin = Good Times

P / S

If there is a Source section, then this is a translation from the source that is linked to the original post in this section. These are the articles I select + search + synthesized from Google in the process of handling issues when making real + projects useful and interesting for myself. => Translated as an article to rummage through when necessary. Therefore, when reading the article, everyone should note:

1. You can go to the source to read the original article (extremely recommend).

2. Translated article => Can not avoid misunderstanding, omission, confusion due to differences in language, context as well as understanding of the translator => We hope you can leave comments to complete the problem.

3. The translation is for reference only + has the true meaning of a translated article requested by the company.

4. Hope the article helps you a bit (I hope so!). =)))))))

Share the news now

Source : Viblo