Thread
#compose
    r

    Rafiul Islam

    1 year ago
    What am I doing wrong with observeAsState() method. See details in the thread.
    I have something like below in ViewModel
    private val _loginResponse: MutableLiveData<Resource<LoginResponse>> = MutableLiveData()
    val loginResponse: LiveData<Resource<LoginResponse>> = _loginResponse
    And I am observing it in UI
    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.
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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

    Rafiul Islam

    1 year ago
    This is my composable function
    @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 23:56:42.280 7167-7167/com.example.drivebox W/IInputConnectionWrapper: getTextBeforeCursor on inactive InputConnection 2021-08-21 23:56:42.292 7167-7167/com.example.drivebox W/IInputConnectionWrapper: getSelectedText on inactive InputConnection 2021-08-21 23:56:42.293 7167-7167/com.example.drivebox W/IInputConnectionWrapper: getTextAfterCursor on inactive InputConnection 2021-08-21 23:56:42.298 7167-7167/com.example.drivebox W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection 2021-08-21 23:56:42.298 7167-7167/com.example.drivebox W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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

    Rafiul Islam

    1 year ago
    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?
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    How do you want to show it in the UI? Text somewhere, in a snackbar, etc?
    r

    Rafiul Islam

    1 year ago
    As a text.
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    You don’t need to do anything special then.
    r

    Rafiul Islam

    1 year ago
    No change? So I should keep it the way it is now?
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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.