user login using viewmodel and retrofit

前端 未结 3 1266
Happy的楠姐
Happy的楠姐 2020-12-06 08:58

im trying to do login using retrofit and viewmodel

i have done successfully login with only retrofit...referred this tutorial--> https://www.youtube.com/watch?v=j0

相关标签:
3条回答
  • 2020-12-06 09:07

    ViewModel is nothing but a mediator. It just hold data with it's own lifecycle. If you are trying to follow MVVM, you have to clean your code first. Make data source, viewmodel and view seperate. All of them has seperate task to do. For better understanding about MVVM please follow this link Following code may help you:

    Create a LoginDataSource

    class LoginDataSource(private val context: Context) {
    
        interface LoginCallBack {
            fun onSuccess();
            fun onError(message: String?)
        }
    
        fun login(email: String, password: String, loginCallBack: LoginCallBack) {
            RetrofitClient.instance.userLogin(email, password)
                    .enqueue(object : Callback<LoginResponse> {
                        override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
                            loginCallBack.onError(t.localizedMessage)
                        }
    
                        override fun onResponse(
                                call: Call<LoginResponse>,
                                response: Response<LoginResponse>
                        ) {
                            var res = response
                            if (res.body()?.status==200) {
    
                                SharedPrefManager.getInstance(context)
                                        .saveUser(response.body()?.data!!)
                                loginCallBack.onSuccess()
                            } else {
                                try {
                                    val jObjError = JSONObject(response.errorBody()!!.string())
                                    loginCallBack.onError(jObjError.getString("user_msg"))
    
                                } catch (e: Exception) {
                                    loginCallBack.onError(e.message)
                                }
                            }
                        }
                    })
        }
    }
    

    Then create a ViewModelFactory

    class LoginViewModelFactory(val loginDataSource: LoginDataSource) : ViewModelProvider.Factory {
    
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return modelClass.getConstructor(LoginDataSource::class.java)
                    .newInstance(loginDataSource)
        }
    }
    

    ViewModel class:

    class LoginViewModel(private val loginDataSource: LoginDataSource) : ViewModel() {
    
        val loginSuccess = MutableLiveData<Boolean>()
        val loginFailedMessage = MutableLiveData<String?>()
    
        fun login(email: String, password: String) {
            loginDataSource.login(email, password, object: LoginDataSource.LoginCallBack {
                override fun onSuccess() {
                    loginSuccess.postValue(true)
                }
    
                override fun onError(message: String?) {
                    loginSuccess.postValue(false)
                    loginFailedMessage.postValue(message)
                }
            })
        }
    }
    

    Finally Activity class:

    class LoginActivity : BaseClassActivity() {
    
        private lateinit var viewModel: LoginViewModel
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.login_activity)
    
            val dataSource = LoginDataSource(applicationContext)
            viewModel = ViewModelProvider(this, LoginViewModelFactory(dataSource)).get(LoginViewModel::class.java)
    
            val button = findViewById<ImageView>(R.id.plusbutton)
            val forgotpassword = findViewById<TextView>(R.id.forgotpassword)
    
            viewModel.loginSuccess.observe(this, Observer {
                if(it) {
                    val intent = Intent(applicationContext, HomeActivity::class.java)
                    intent.flags =
                            Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                    startActivity(intent)
                    finish()
                }
            })
    
            viewModel.loginFailedMessage.observe(this, Observer {
                showToast(applicationContext, it)
            })
    
            button.setOnClickListener {
                val i = Intent(applicationContext, RegisterActivity::class.java)
                startActivity(i)
            }
            forgotpassword.setOnClickListener {
                val i = Intent(applicationContext, ForgotPassword::class.java)
                startActivity(i)
            }
    
            loginbtn.setOnClickListener {
                val email = loginuser.text.toString().trim()
                val password = loginpassword.text.toString().trim()
    
                if (email.isEmpty()) {
                    Toast.makeText(
                            applicationContext, "Data is missing", Toast.LENGTH_LONG
                    ).show()
                    loginuser.error = "Email required"
                    loginuser.requestFocus()
                    return@setOnClickListener
                } else if (password.isEmpty()) {
                    loginpassword.error = "Password required"
                    loginpassword.requestFocus()
                    return@setOnClickListener
                } else {
                    viewModel.login(email, password)
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-06 09:22
    class LoginActivity : BaseClassActivity() {
        private val viewModel by viewModels<LoginViewModel>()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.login_activity)
    
            val button = findViewById<ImageView>(R.id.plusbutton)
            val forgotpassword = findViewById<TextView>(R.id.forgotpassword)
    
            button.setOnClickListener {
                val i = Intent(applicationContext, RegisterActivity::class.java)
                startActivity(i)
            }
    
            forgotpassword.setOnClickListener {
                val i = Intent(applicationContext, ForgotPassword::class.java)
                startActivity(i)
            }
    
            loginuser.onTextChanged {
                viewModel.user.value = it.toString()
            }
    
            loginpassword.onTextChanged {
                viewModel.password.value = it.toString()
            }
    
            loginbtn.setOnClickListener {
                viewModel.login()
            }
    
            viewModel.loginResult.observe(this) { result ->
                when (result) {
                    UserMissing -> {
                        Toast.makeText(
                            applicationContext, "Data is missing", Toast.LENGTH_LONG
                        ).show()
                        loginuser.error = "Email required"
                        loginuser.requestFocus()
                    }
                    PasswordMissing -> {
                        loginpassword.error = "Password required"
                        loginpassword.requestFocus()
                    }
                    NetworkFailure -> {
                    }
                    NetworkError -> {
                        showToast(applicationContext, result.userMessage)
                    }
                    Success -> {
                        val intent = Intent(applicationContext, HomeActivity::class.java)
                        intent.flags =
                            Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                        showToast(applicationContext, res.body()?.message)
                        Log.d("kjsfgxhufb", response.body()?.status.toString())
                        startActivity(intent)
                        finish()
                    }
                }.safe()
            }
        }
    }
    
    class LoginViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
        sealed class LoginResult {
            object UserMissing : LoginResult(),
    
            object PasswordMissing : LoginResult(),
    
            class NetworkError(val userMessage: String) : LoginResult(),
    
            object NetworkFailure : LoginResult(),
    
            object Success : LoginResult()
        }
    
        val user: MutableLiveData<String> = savedStateHandle.getLiveData("user", "")
        val password: MutableLiveData<String> = savedStateHandle.getLiveData("password", "")
    
        private val loginResultEmitter = EventEmitter<LoginResult>()
        val loginResult: EventSource<LoginResult> = loginResultEmitter
    
        fun login() {
            val email = user.value!!.toString().trim()
            val password = password.value!!.toString().trim()
    
            if (email.isEmpty()) {
                loginResultEmitter.emit(LoginResult.UserMissing)
                return
            }
    
    
            if (password.isEmpty()) {
                loginResultEmitter.emit(LoginResult.PasswordMissing)
                return
            }
    
            RetrofitClient.instance.userLogin(email, password)
                .enqueue(object : Callback<LoginResponse> {
                    override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
                        Log.d("res", "" + t)
                        loginResultEmitter.emit(LoginResult.NetworkFailure)
                    }
    
                    override fun onResponse(
                        call: Call<LoginResponse>,
                        response: Response<LoginResponse>
                    ) {
                        var res = response
    
                        Log.d("response check ", "" + response.body()?.status.toString())
                        if (res.body()?.status == 200) {
                            SharedPrefManager.getInstance(applicationContext).saveUser(response.body()?.data!!)
                            loginResultEmitter.emit(LoginResult.Success)
                        } else {
                            try {
                                val jObjError =
                                    JSONObject(response.errorBody()!!.string())
                                loginResultEmitter.emit(LoginResult.NetworkError(jObjError.getString("user_msg")))
                            } catch (e: Exception) {
                                // showToast(applicationContext,e.message) // TODO
                                Log.e("errorrr", e.message)
                            }
                        }
                    }
                })
        }
    }
    

    Using

    allprojects {
        repositories {
            // ...
            maven { url "https://jitpack.io" }
        }
        // ...
    }
    
    implementation 'com.github.Zhuinden:live-event:1.1.0'
    

    EDIT: some missing blocks to actually make this compile:

    fun <T> T.safe(): T = this // helper method
    

    These dependencies in Gradle

    implementation "androidx.core:core-ktx:1.3.2"
    implementation "androidx.activity:activity-ktx:1.1.0"
    implementation "androidx.fragment:fragment-ktx:1.2.5"
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"
    

    Also add

    android {
      compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
      }
    
      kotlinOptions {
        jvmTarget = "1.8"
      }
    }
    

    To access applicationContext in ViewModel, you'll need to use AndroidViewModel instead of ViewModel

    class LoginViewModel(
        private val application: Application,
        private val savedStateHandle: SavedStateHandle
    ): AndroidViewModel(application) {
        private val applicationContext = application
    

    And that should fix it

    EDIT: apparently the "onTextChanged" is doAfterTextChanged in ktx, what I use is this:

    inline fun EditText.onTextChanged(crossinline textChangeListener: (String) -> Unit) {
        addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(editable: Editable) {
                textChangeListener(editable.toString())
            }
    
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            }
    
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            }
        })
    }
    
    0 讨论(0)
  • 2020-12-06 09:23

    @EpicPandaForce we moved the discussion from the comments from there: https://stackoverflow.com/a/64365692/2448589

    OP is trying to figure out what is going on in your code and I want to clarify a bit. What I must say is that this code split between Activity that is in charge of just passing the data from the user interactions to ViewModel and is observing the results is the first good step in composing the code properly.

    @EpicPandaForce has used Kotlin delegated property by viewModels() which is a nice shortcut that doesn't require to use ViewModelProviders.of(...).

    Another thing is that the API call is made in the ViewModel, which is another good step, however I would pass it via the constructor of ViewModel to make the code testable and it will be a good step to eventually have Dependency Inversion Principle fulfilled.

    Last thing I like and do on my own is the sealed class LoginResult which improves readability of the State in which we are in certain situations and gives us ability to easily pass some payloads to the Activity.

    The one thing that is missing are the dependencies and the gradle configuration, because the library that provide by viewModels() and SavedStateHandle is targeting Java 8 bytecode. Add this to your build.gradle file in app module:

    android {
      ...
      // Configure only for each module that uses Java 8
      // language features (either in its source code or
      // through dependencies).
      compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
      }
      // For Kotlin projects
      kotlinOptions {
        jvmTarget = "1.8"
      }
    }
    
    0 讨论(0)
提交回复
热议问题