简体   繁体   中英

Migrate to ObserveAsState in Jetpack Compose - null

I am a beginner trying to learn Kotlin by changing an old tutorial to Compose.

I have a ViewModel with


private val _registerStatus = MutableLiveData<Resource<String>>()
    val registerStatus: LiveData<Resource<String>> = _registerStatus

    fun register(email: String, password: String, repeatedPassword: String) {
        _registerStatus.postValue(Resource.loading(null))
        if(email.isEmpty() || password.isEmpty() || repeatedPassword.isEmpty()) {
            _registerStatus.postValue(Resource.error("Please fill out all the fields", null))
            return
        }
        if(password != repeatedPassword) {
            _registerStatus.postValue(Resource.error("The passwords do not match", null))
            return
        }
        viewModelScope.launch {
            val result = repository.register(email, password)
            _registerStatus.postValue(result)
        }
    }

and Fragment with:

 viewModel.registerStatus.observe(viewLifecycleOwner, Observer { result ->
        result?.let {
            when(result.status) {
                Status.SUCCESS -> {
                    registerProgressBar.visibility = View.GONE
                    showSnackbar(result.data ?: "Successfully registered an account")
                }
                Status.ERROR -> {
                    registerProgressBar.visibility = View.GONE
                    showSnackbar(result.message ?: "An unknown error occurred")
                }
                Status.LOADING -> {
                    registerProgressBar.visibility = View.VISIBLE
                }
            }
        }
    })

How could I adapt this code to use Jetpack Compose?

I understand I need to use "ObserveAsState" in a Composable:

registerViewModel.registerStatus.observeAsState()

Truth is, I think I don't really understand the nullable issue, or what val result is doing, or what result -> result?.let is doing, except being some way to pass a non-null value in? If removed, I must make registerStatus.status non-null or safe. So I can do the below:

@Composable
fun subscribeToObservers() {
  val registerStatus by registerViewModel.registerStatus.observeAsState()
when(registerStatus!!.status){
            Status.SUCCESS -> {

            }
            Status.ERROR -> {

            }
            Status.LOADING -> {

            }
         }
    }

, or do I need to get the "result" value over?

Anything to help me toward understanding the issues better would be really appreciated.

You should avoid using !! as that means you know that that variable is never null. Your application is going to crash if the variable is ever null when going through this piece of code.

I want also to note that logically, you might not be able to find a scenario in which your variable is going to be null so you might get tempted to use !! but it is better to avoid it just in case.

Using nullableVariable?.let { -> nonNullVariable } is much safer as it only runs if the variable is not null. Unlike !! , it won't cause a crash if the variable is null.

I would write the code like this:

    @Composable
    fun subscribeToObservers() {
        val registerStatus by registerViewModel.registerStatus.observeAsState()
        registerStatus?.let { nonNullRegisterStatus ->
            when(nonNullRegisterStatus) {
                Status.SUCCESS -> {}
                Status.ERROR -> {}
                Status.LOADING -> {}
            }
        }
    }

There are two ways you can approach this:

Compose style

ViewModel

private val _registerStatus : MutableState<Resource<String>> = mutableStateOf(Resource.init(null))
val registerStatus: State<Resource<String>> = _registerStatus

fun register(email: String, password: String, repeatedPassword: String) {
    _registerStatus.value = Resource.loading(null)
    if(email.isEmpty() || password.isEmpty() || repeatedPassword.isEmpty()) {
        _registerStatus.value = Resource.error("Please fill out all the fields", null)
        return
    }
    if(password != repeatedPassword) {
        _registerStatus.value = Resource.error("The passwords do not match", null)
        return
    }
    viewModelScope.launch {
        val result = repository.register(email, password)
        _registerStatus.value = result
    }
}

Composable

@Composable
fun subscribeToObservers() {
    val registerStatus by viewModel.registerStatus
    when(registerStatus.status){
        Status.INIT -> {}
        Status.SUCCESS -> {}
        Status.ERROR -> {}
        Status.LOADING -> {}
    }
}

You can make _registerStatus not null in this way or if you initialize it as private val _registerStatus: MutableState<Resource<String>?> = mutableStateOf(null) handle null like:

@Composable
fun subscribeToObservers() {
    val registerStatus by viewModel.registerStatus
    when(registerStatus.status){
        Status.INIT -> {}
        Status.SUCCESS -> {}
        Status.ERROR -> {}
        Status.LOADING -> {}
        null -> {}
    }
}

Kotlin style

ViewModel

private val _registerStatus : MutableStateFlow<Resource<String>> = mutableStateOf(Resource.init(null))
val registerStatus: StateFlow<Resource<String>> = _registerStatus

fun register(email: String, password: String, repeatedPassword: String) {
    _registerStatus.tryEmit(Resource.loading(null))
    if(email.isEmpty() || password.isEmpty() || repeatedPassword.isEmpty()) {
    _registerStatus.tryEmit(Resource.error("Please fill out all the fields", null))
        return
    }
    if(password != repeatedPassword) {
        _registerStatus.tryEmit(Resource.error("The passwords do not match", null))
        return
    }
    viewModelScope.launch {
        val result = repository.register(email, password)
        _registerStatus.tryEmit(result)
    }
}

Composable

@Composable
fun subscribeToObservers() {
    val registerStatus by viewModel.registerStatus.collectAsState()
    when(registerStatus.status){
        Status.INIT -> {}
        Status.SUCCESS -> {}
        Status.ERROR -> {}
        Status.LOADING -> {}
        null -> {}
    }
}

MutableStateFlow are thread safe and kotlin's solution to xJava This method is thread-safe and can be safely invoked from concurrent coroutines without external synchronization. Kotlin provides one more type of flow called SharedFlow and it's upon your use case to use SharedFlow or StateFlow .
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
The main difference between SharedFlow and StateFlow

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM