I have a problem with the code in ViewModel + Kotlin Coroutines + LiveData

116 Views Asked by At

I have a ViewModel

@HiltViewModel
class LoginViewModel @Inject constructor(
    private val apiRepository: ApiRepository
) : ViewModel() {
    private val account = MutableLiveData<String>("123")

    private val password = MutableLiveData<String>("123")

    val message: MutableLiveData<String> = MutableLiveData()

    var loginResult: LiveData<Resource<UserInfo>> = MutableLiveData()

    fun signIn() {
        if (TextUtils.isEmpty(account.value)) {
            message.postValue("Please enter your account")
            return
        }

        if (TextUtils.isEmpty(password.value)) {
            message.postValue("please enter your password")
            return
        }

        // In this code, it doesn’t work. I think it’s because I didn’t observe it.  
        // Is there any better way to write it here?
        loginResult = apiRepository.signIn(account.value!!, password.value!!)
    }

    fun inputAccount(accountValue: String) {
        account.value = accountValue
    }

    fun inputPassword(passwordValue: String) {
        password.value = passwordValue
    }
}

This is my interface

@AndroidEntryPoint
class LoginActivity : BaseActivity<ActivityLoginBinding>() {
    private val viewModel: LoginViewModel by viewModels()

    ......

    override fun initEvent() {
        binding.account.editText!!.addTextChangedListener { viewModel.inputAccount(it.toString()) }

        binding.password.editText!!.addTextChangedListener { viewModel.inputPassword(it.toString()) }

        binding.signIn.setOnClickListener {
            viewModel.signIn()
        }
    }

    override fun setupObservers() {
        viewModel.message.observe(this) {
            Snackbar.make(binding.root, it, Snackbar.LENGTH_SHORT).show()
        }

         /**
         * There will be no callback here, I know it’s because I’m observing
         * `var loginResult: LiveData<Resource<UserInfo>> = MutableLiveData()`
         * instead of `apiRepository.signIn(account.value!!, password.value!!)` 
         * because it was reassigned
         */
        viewModel.loginResult.observe(this) {
            Log.d("TAG", "setupObservers: $it")
        }
    }
}

So I adjusted the code a bit LoginViewModel.signIn

fun signIn(): LiveData<Resource<UserInfo>>? {
    if (TextUtils.isEmpty(account.value)) {

        message.postValue("Please enter your account")
        return null
    }
    if (TextUtils.isEmpty(password.value)) {

        message.postValue("please enter your password")
        return null
    }

    return apiRepository.signIn(account.value!!, password.value!!)
}

LoginActivity.initEvent

override fun initEvent() {
    binding.signIn.setOnClickListener {
        viewModel.signIn()?.observe(this) {
            Log.d("TAG", "setupObservers: $it")
        }
    }
}

I have checked the official documents of LiveData, and all call livedata{} during initialization. There has been no re-assignment, but if you log in, you cannot directly start the application and request the network.

coroutines doucument

Although I finally achieved my results, I think this is not the best practice, so I want to ask for help!

  • Supplementary code ApiRepository
class ApiRepository @Inject constructor(
    private val apiService: ApiService
) : BaseRemoteDataSource() {
    fun signIn(account: String, password: String) =
        getResult { apiService.signIn(account, password) }
}

BaseRemoteDataSource

abstract class BaseRemoteDataSource {
    protected fun <T> getResult(call: suspend () -> Response<T>): LiveData<Resource<T>> =
        liveData(Dispatchers.IO) {
            try {
                val response = call.invoke()
                if (response.isSuccessful) {
                    val body = response.body()
                    if (body != null) emit(Resource.success(body))
                } else {
                    emit(Resource.error<T>(" ${response.code()} ${response.message()}"))
                }
            } catch (e: Exception) {
                emit(Resource.error<T>(e.message ?: e.toString()))
            }
        }
}
1

There are 1 best solutions below

0
On

Or i write like this

fun signIn() {
    if (TextUtils.isEmpty(account.value)) {
        message.postValue("Please enter your account")
        return
    }
    if (TextUtils.isEmpty(password.value)) {
        message.postValue("please enter your password")
        return
    }
    viewModelScope.launch {
        repository.signIn(account.value, password.value).onEach {
            loginResult.value = it
        }
    }
}

But I think this is not perfect