What am I doing wrong with observeAsState() method...
# compose
r
What am I doing wrong with observeAsState() method. See details in the thread.
I have something like below in ViewModel
Copy code
private val _loginResponse: MutableLiveData<Resource<LoginResponse>> = MutableLiveData()
val loginResponse: LiveData<Resource<LoginResponse>> = _loginResponse
And I am observing it in UI
Copy code
val loginResponse by viewModel.loginResponse.observeAsState()

when (loginResponse) {
    is Resource.Success -> {
        Log.d("TAG", "Login: Success")
    }
    is Resource.Failure -> {
        Log.d("TAG", "Login: Failed")
}
    else -> {}
}
but the logs keep getting called even when I am typing in the username field in the login form.
z
Doesn't look like you're doing anything wrong. Probably the composable function that you're logging from is being recomposed to handle the text updates.
Logging events from the view model like this should probably be done in the view model itself
r
This is my composable function
Copy code
@Composable
fun Login(navController: NavHostController, viewModel: LoginViewModel) {
    
    val (username, onUsernameChange) = remember { mutableStateOf("") }
    val (password, onPasswordChange) = remember { mutableStateOf("") }

    val loginResponse by viewModel.loginResponse.observeAsState()

    when (loginResponse) {
        is Resource.Success -> {
            Log.d("TAG", "Login: Success")
        }
        is Resource.Failure -> {
            Log.d("TAG", "Login: Failed")
    }
        else -> {}
    }
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(25.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.Start
    ) {
        TextField(
            modifier = Modifier.fillMaxWidth(),
            value = username,
            onValueChange = onUsernameChange,
            placeholder = {
                Text("Username")
            },
            leadingIcon = {
                Icon(Icons.Default.Person, null)
            },
            colors = TextFieldDefaults.textFieldColors(
                backgroundColor = Color(0xfff4f8f7),
                focusedIndicatorColor = Color.Transparent,
                unfocusedIndicatorColor = Color.Transparent,
                disabledIndicatorColor = Color.Transparent
            ),
            shape = RoundedCornerShape(10)
        )
        Spacer(Modifier.height(20.dp))
        TextField(
            modifier = Modifier.fillMaxWidth(),
            value = password,
            onValueChange = onPasswordChange,
            placeholder = {
                Text("Password")
            },
            leadingIcon = {
                Icon(Icons.Default.Lock, null)
            },
            colors = TextFieldDefaults.textFieldColors(
                backgroundColor = Color(0xfff4f8f7),
                focusedIndicatorColor = Color.Transparent,
                unfocusedIndicatorColor = Color.Transparent,
                disabledIndicatorColor = Color.Transparent
            ),
            shape = RoundedCornerShape(10),
            visualTransformation = PasswordVisualTransformation()
        )
        Spacer(Modifier.height(20.dp))
        Box(modifier = Modifier.fillMaxWidth()) {
            Button(
                onClick = {
                    viewModel.login(
                        username, password
                    )
                },
                modifier = Modifier.fillMaxWidth(),
                contentPadding = PaddingValues(14.dp)
            ) {
                Text(
                    text = "Next",
                    color = Color.White
                )
            }
            Icon(
                modifier = Modifier
                    .align(Alignment.CenterEnd)
                    .padding(end = 16.dp),
                imageVector = Icons.Default.ArrowForward,
                contentDescription = "GoArrow"
            )
        }
    }
}
I don't understand how it can be recomposed due to onUsernameChange or onPasswordChange event. Shouldn't it just update the Textfield? Is there anything to do with Inputmanager? My log when I start typing D/Compose Focus: Owner FocusChanged(true) 2021-08-21 235642.280 7167-7167/com.example.drivebox W/IInputConnectionWrapper: getTextBeforeCursor on inactive InputConnection 2021-08-21 235642.292 7167-7167/com.example.drivebox W/IInputConnectionWrapper: getSelectedText on inactive InputConnection 2021-08-21 235642.293 7167-7167/com.example.drivebox W/IInputConnectionWrapper: getTextAfterCursor on inactive InputConnection 2021-08-21 235642.298 7167-7167/com.example.drivebox W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection 2021-08-21 235642.298 7167-7167/com.example.drivebox W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection
z
When you change a state value, any composable function in which that state was read will be recomposed. If you have a
state: State<T>
, “reading” that state value means reading
state.value
. When you use the state as a property delegate (i.e.
by state
), then that uses a static method that looks something like
fun State<T>.get(): T = value
, so in that case just reading the property itself reads the state. However, when you destructure a
state
, it calls
state.component1()
, which looks like
fun component1():T  = value
, which means it reads the state immediately. To bring this back to your code, inside of
Login
, when you do
val (username, onUsernameChanged) = remember { mutableStateOf("") }
, that is both creating the state object and immediately reading it. This means that any time the username changes, the
Login
function will be recomposed, and that recomposition is what’s triggering the extra log statements.
However, even if you get rid of that state read, it’s a very bad idea to rely on any expectations of how often any particular function will be recomposed, because the compose runtime doesn’t make any guarantees there.
r
Thank you for explaining in detail. The explanation is really helpful. I think I should read more about the state. Can you just tell me how can I observe the error and show it to the UI in an appropriate way?
z
How do you want to show it in the UI? Text somewhere, in a snackbar, etc?
r
As a text.
z
You don’t need to do anything special then.
r
No change? So I should keep it the way it is now?
z
Well you’re not showing the error in a
Text
now, but all you need to do is add that composable in wherever you want to show it.
538 Views