Playing around with Android Compose, just want to build a simple login page that navigates to home page on success.
Below is code for ViewModel and login screen
enum class UserStatus {
    LOADING,
    SUCCESS,
    EMPTY
}
data class LoginUser(
    val token: String,
    val status: UserStatus,
)
class LoginViewModel(
    private val userRepository: DefaultUserRepository = DefaultUserRepository(),
) : ViewModel() {
    val loginUser by lazy { MutableLiveData<LoginUser>() }
    fun login(email: String, password: String) {
        loginUser.postValue(LoginUser("", UserStatus.LOADING))
        val loginBody = LoginBody(email = email, password = password)
        viewModelScope.launch(Dispatchers.IO) {
            val loginResponse = userRepository.login(loginBody)
            loginUser.postValue(LoginUser(loginResponse.access_token, UserStatus.SUCCESS))
        }
    }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginScreen(
    navController: NavController,
    viewModel: LoginViewModel = LoginViewModel(),
) {
    var email = remember { mutableStateOf("") }
    var password = remember { mutableStateOf("") }
    val userState by viewModel.loginUser.observeAsState(LoginUser("", UserStatus.EMPTY))
    when (userState.status) {
        UserStatus.SUCCESS -> navController.navigate("home/" + userState!!.token)
        UserStatus.LOADING -> CircularProgressIndicator()
        UserStatus.EMPTY -> Box(
            contentAlignment = Alignment.Center,
            modifier = Modifier.fillMaxSize(),
        ) {
            Column(
                modifier = Modifier.align(Alignment.Center),
            ) {
                Text(
                    text = "login",
                    fontSize = 50.sp,
                )
                OutlinedTextField(
                    value = email.value,
                    onValueChange = { email.value = it },
                    label = { Text("Email") }
                )
                OutlinedTextField(
                    value = password.value,
                    onValueChange = { password.value = it },
                    label = { Text("Password") },
                    visualTransformation = PasswordVisualTransformation(),
                    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
                )
                Spacer(modifier = Modifier.height(10.dp))
                Row() {
                    Text(
                        text = "No account? ",
                        fontSize = 18.sp,
                    )
                    ClickableText(
                        text = AnnotatedString("Signup here"),
                        onClick = { navController.navigate("signup") }
                    )
                }
                Spacer(modifier = Modifier.height(10.dp))
                FloatingActionButton(
                    onClick = {
                        viewModel.login(email.value, password.value)
                    }
                ) {
                    Text("Login")
                }
            }
        }
    }
And the code for the repository that gets jwt is
@JsonClass(generateAdapter = true)
data class LoginResponse(
    val access_token: String,
    val token_type: String,
)
data class LoginBody(
    val email: String,
    val password: String,
)
class DefaultUserRepository(
    private val client: OkHttpClient = OkHttpClient(),
    private val moshi: Moshi = Moshi.Builder().build(),
) : UserRepository {
    private val loginAdapter = moshi.adapter(LoginResponse::class.java)
    override suspend fun login(loginBody: LoginBody): LoginResponse {
        val loginObject = JSONObject()
        loginObject.put("email", loginBody.email)
        loginObject.put("password", loginBody.password)
        val mediaType = "application/json".toMediaType()
        val body = loginObject.toString().toRequestBody(mediaType)
        val loginRequest = Request.Builder()
            .url(/* auth endpoint, returns jwt */)
            .post(body)
            .build()
        val response = client.newCall(loginRequest).execute()
        if (!response.isSuccessful) throw IOException("Unexpected code $response")
        val loginResponse = loginAdapter.fromJson(response.body!!.source())
        return loginResponse!!
    }
}
The repository works and returns jwt as expected.  When I click the login button, the LiveData variable userState updates to the loading state as expected, displaying a CircularProgressBar, but once the auth request completes it does not update to the success state and navigate to home screen.  The only thing I can think of is that the suspend function in repository is responsible and that the loginResponse used in the postValue is empty, but when I log loginResponse before the postValue call it is populated with jwt.  Any ideas?
