preface

I believe that “most” partners read relevant design pattern books and articles, and they will be able to use them as soon as they are written. They seldom use them in actual development, or they don’t know where to use them, lack of application scenarios, and forget about them over time

In this article, I share that I use the design mode – state mode in Android development, hope you enjoy ~

demand

Suppose we have a requirement (article App)

When the user clicks on the avatar

  • Not logged in: Click your avatar to jump toThe login pageLogging in
  • The login status: Click the profile picture, no operation is taken

coding

Wouldn’t it be so easy to get a demand? Knock out the code

class MainActivity :AppCompatActivity() {private val isLogin: Boolean by Preference("isLogin".false)... Omit some codeoverride fun initView(a) {

        // Click your avatar
        mIvPortrait.setOnClickListener {
            if(isLogin) { startActivity<LoginActivity>() } } } ... Omit some code}Copy the code

Preference encapsulates the SharedPreferences, Kotlin’s delegate syntax, which is not the focus of this article

Save a User Login (isLogin) Boolean variable with SharedPreferences and use this variable to determine whether to jump to the login page.

The logic is relatively simple and seems fine for now, but let’s take a closer look at the extension requirements

Expand demand

When the user clicks on the article favorites

  • Not logged in: Click favorites to jump toThe login pageLogging in
  • The login status: Click on the collection, then initiate the collection request, the article is collected

coding

class MainActivity :AppCompatActivity() {private val isLogin: Boolean by Preference("isLogin".false)... Omit some codeoverride fun initView(a) {

        // Click your avatar
        mIvPortrait.setOnClickListener {
            if (isLogin) {
                startActivity<LoginActivity>()
            }
        }
        
        // Click the favorites button
        mBtnCollect.setOnClickListener {
            if (isLogin) {
                viewModel.collect(articleId)
            } else{ startActivity<LoginActivity>() } } } ... Omit some code}Copy the code

Similarly, in the processing logic of clicking the “favorites” button, we also judge whether to “favorites” according to the isLogin variable, and then carry out the corresponding logical processing. Here we can smell the bad taste of code, repeated logon status judgments.

If we are adding a judgment logic when the user clicks the “share” button to share the article, we add it again with the following code:

// Click the favorites button
mBtnShare.setOnClickListener {
    if (isLogin) {
        viewModel.share(articleId)
    } else {
        startActivity<LoginActivity>()
    }
}
Copy the code

In this way, our Android project will be full of log-in logic judgment, which will greatly reduce the quality of the code, which is not conducive to subsequent development and maintenance.

thinking

What would you have done?

CV code of course, it is not impossible to use 🙂

If you CV code, then you are sure to be in the mouth of colleagues/bosses: ***

Without further ado, enter the body, design mode – state mode

The state pattern

Description: The behavior in the state mode is determined by the state, and different states have different behaviors. Definition: Allows an object to change its behavior when its internal state changes. The object appears to change to its class.

As mentioned above, when the user logs in, there will be favorites, likes, shares and other operations. If the user has not logged in, go to the login page and perform subsequent operations.

The change in the user’s state (not logged in -> logged in) is internal to an object, and external is imperceptive. But the actual behavior changes as the state changes.

This might be a little convoluted. Let’s take a look at the actual code demo

UserState

interface UserState {

    fun collect(context: Context? , block: () -> Unit)
    
    fun share(context: Context? , block: () -> Unit)

    fun login(context: Context?).
}
Copy the code

Create a UserState interface class, which defines login, Collect and share methods (behavior). Then create LoginState and LogoutState classes respectively. And the implementation of UserState interface

LoginState

class LoginState : UserState {

    override fun collect(context: Context? , block: () -> Unit) {
        // Start a collection
        block()
    }
    
    override fun share(context: Context? , block: () -> Unit) {
        // Initiate sharing
        block()
    }

    // Login status No login is required
    override fun login(context: Context?).{}}Copy the code

In the logon state, we perform the corresponding logical operation. In the logon state, there is no need to log in, so there is no operation here

LogoutState

class LogoutState : UserState {

    / / collection
    override fun collect(context: Context? , block: () -> Unit) {
        goLoginActivity(context)
    }
    
    / / share
    override fun share(context: Context? , block: () -> Unit) {
        goLoginActivity(context)
    }

    / / login
    override fun login(context: Context?). {
        goLoginActivity(context)
    }
    
    // Jump to login
    private fun goLoginActivity(context: Context?).{ context? .run { toast(getString(R.string.please_login)) startActivity<LoginActivity>() } } }Copy the code

Here I believe you have also guessed, not logged in state, unified jump to the login page for login, in the subsequent operations

Then we’re going to define a state management class, UserContext, to manage our user state

UserContext

object UserContext{

    // Persist the login state
    private var isLogin: Boolean by Preference(Key.LOGIN, false)

    // Set the default state
    var mState: UserState = if (isLogin) LoginState() else LogoutState()

    / / collection
    fun collect(context: Context? , block: () -> Unit) {
        mState.collect(context, block)
    }
    
     / / share
    fun share(context: Context? , block: () -> Unit) {
        mState.share(context, block)
    }

    / / login
    fun login(context: Activity?). {
        mState.login(context)
    }
    
    // Switch to the login state
    fun setLoginState(a){
        // Change sharedPreferences isLogin value
        isLogin = true
        mState = LoginState()
    }
    
    // Switch to the unlogged state
    fun setLogoutState(a){
        // Change sharedPreferences isLogin value
        isLogin = false
        mState = LogoutState()
    }
}
Copy the code

The UserContext class manages the user’s state. The mState variable is initialized to the unlogged state by default, and the method for switching between the logged state and the unlogged state is declared. Finally, back to MainActivity, let’s see how it is used in practice.

MainActivity

class MainActivity :AppCompatActivity() {//private val isLogin: Boolean by Preference("isLogin", false). Omit some codeoverride fun initView(a) {

        // Click your avatar
        mIvPortrait.setOnClickListener {
            // Invoke login
            UserContext.login(this)}// Click the favorites button
        mBtnCollect.setOnClickListener {
            // Call favorites
            UserContext.collect(this) {
                viewModel.collect(articleId)
            }
        }
        
        mBtnShare.setOnClickListener {
            // Call share
            UserContext.share(this) { viewModel.share(articleId) } } } ... Omit some code}Copy the code

In MainActivity, we can gracefully log in, bookmark, and share without if-else.

As for switching states, you can call UserContext to switch states during a successful login callback or exit login. Is the following code:

// Click the log out button
mBtnLogout.setOnClickListener {
    // Set the current state to unlogged
    UserContext.setLogoutState()
}
Copy the code
// Successful login callback
private fun loginSuccess(a) {
    // Set the current state to login state
    UserContext.setLoginState()
}
Copy the code

There is no need to change the state of the MainActivity, but the behavior inside the MainActivity changes.

The last

At this point, it is a state of complete pattern implementation, from the start, don’t use the design patterns, the code is filled with the if – else logic judgment, the lack of aesthetic feeling, to use the design patterns back, before and after contrast, although increased, but the complicated state, into a state of structure is clear class, so as to avoid duplicated code, It also ensures scalability and maintainability. Feel the charm of state mode

Why don’t you put it to work? 🙂

The last

At this point, there must be some smart friends who think, if I encapsulate a general method, the effect is the same.

The code is as follows:

object UserContext{

     // Persist the login state
    private var isLogin: Boolean by Preference(Key.LOGIN, false)
    
    private fun execute(context: Context, block: () -> Unit) {
        if (isLogin) {
            block()
        } else {
            startActivity<LoginActivity>()
        }
    }
}
Copy the code

At first glance, this seems to be the case, with the same effect and fewer classes created.

But there are only two user states — logged in and logged off.

What if, instead, you add a user status, a feature that only administrators can share?

The code should end up like this:


object UserContext{

     // Persist the login state
    private var isLogin: Boolean by Preference(Key.LOGIN, false)
    
    private fun execute(context: Context, block: () -> Unit) {
        if (isLogin) {
            block()
        } else {
            startActivity<LoginActivity>()
        }
    }
    
    private fun adminExecute(context: Context, block: () -> Unit) {
        if (isLogin) {
    
            if (isAdmin) {
                block()
                return
            }
    
            toast("Unable to share, no permissions.")}else {
            startActivity<LoginActivity>()
        }
    }
}

Copy the code

Add another if-else to the code, I think I smell something bad:

The state mode, on the other hand, not only eliminates if-else logic, making the structure clearer, but also makes the module more flexible

Just create a new state class, AdminState

Which is better? You can tell at a glance