Joost Klitsie
07/19/2020, 8:47 AMJoost Klitsie
07/19/2020, 8:58 AMJoost Klitsie
07/19/2020, 8:58 AMmy name is joost
Joost Klitsie
07/19/2020, 8:59 AMm name ist jooyt
which is very weird 😄Joost Klitsie
07/19/2020, 8:59 AMJoost Klitsie
07/19/2020, 9:10 AMMutableStateFlow<ViewState>
, where the userName is just a String in the ViewState and it is updated as such:
viewState.value = viewState.value.copy(
userName = userName,
isButtonEnabled = !userName.isBlank() && !viewState.value.password.isNullOrBlank()
)
Joost Klitsie
07/19/2020, 9:10 AMAdam Powell
07/19/2020, 2:48 PMDispatchers.Main
isn't the intended end state but at worst it should be incurring a single frame of latency before changes are reflected in the UI, not affecting correctness. Can you post some more of the related code you're using to update the StateFlow?Joost Klitsie
07/19/2020, 2:50 PMJoost Klitsie
07/19/2020, 2:52 PMJoost Klitsie
07/19/2020, 2:52 PMJoost Klitsie
07/19/2020, 2:53 PMJoost Klitsie
07/19/2020, 2:54 PMJoost Klitsie
07/19/2020, 2:55 PMAdam Powell
07/19/2020, 2:58 PMJoost Klitsie
07/19/2020, 2:59 PMAdam Powell
07/19/2020, 2:59 PMJoost Klitsie
07/19/2020, 2:59 PMJoost Klitsie
07/19/2020, 2:59 PMJoost Klitsie
07/19/2020, 3:00 PMJoost Klitsie
07/19/2020, 3:00 PMAdam Powell
07/19/2020, 3:00 PMJoost Klitsie
07/19/2020, 3:01 PMJoost Klitsie
07/19/2020, 3:01 PMAdam Powell
07/19/2020, 3:01 PMJoost Klitsie
07/19/2020, 3:01 PMJoost Klitsie
07/19/2020, 3:02 PMOutlinedTextField(
value = viewState.userName ?: "",
onValueChange = viewModel::updateUserName,
modifier = Modifier.fillMaxWidth(),
label = { Text("User name") }
)
Joost Klitsie
07/19/2020, 3:02 PMJoost Klitsie
07/19/2020, 3:02 PMJoost Klitsie
07/19/2020, 3:02 PMoverride val viewState = MutableStateFlow(LoginViewState())
override fun updateUserName(userName: String) {
viewState.update {
copy(
userName = userName,
isButtonEnabled = viewState.value.password.isNotNullOrBlack() && userName.isNotNullOrBlack()
)
}
}
Joost Klitsie
07/19/2020, 3:02 PMdata class LoginViewState(
override var userName: String? = null,
override var userNameError: String? = null,
override var password: String? = null,
override var passwordError: String? = null,
override var isLoading: Boolean = false,
override var isButtonEnabled: Boolean = false
): LoginContract.ViewState
Joost Klitsie
07/19/2020, 3:03 PMfun <T> MutableStateFlow<T>.update(
block: T.() -> T
) {
value = value.run(block)
}
Joost Klitsie
07/19/2020, 3:03 PM@Composable
fun LoginInputComponent(props: LoginContract.Props) {
val viewModel by instance<LoginContract.Props, LoginContract.ViewModel>(props)
LoginInput(viewModel.viewState.collectAsState(Dispatchers.Main.immediate).value, viewModel)
}
@Composable
fun LoginInput(viewState: LoginContract.ViewState, viewModel: LoginContract.ViewModel) {
//... OutlinedTextField
Joost Klitsie
07/19/2020, 3:04 PMAdam Powell
07/19/2020, 3:07 PMJoost Klitsie
07/19/2020, 3:07 PMoverride fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val stateFlow = MutableStateFlow(LoginViewState())
val update = { userName: String ->
stateFlow.value = stateFlow.value.copy(userName = userName)
}
setContent {
val viewState = stateFlow.collectAsState().value
OutlinedTextField(
value = viewState.userName ?: "",
onValueChange = update,
modifier = Modifier.fillMaxWidth(),
label = { Text("User name") }
)
}
}
Joost Klitsie
07/19/2020, 3:07 PMJoost Klitsie
07/19/2020, 3:07 PMJoost Klitsie
07/19/2020, 3:08 PMupdate
will run too late (as a handler.post)Joost Klitsie
07/19/2020, 3:08 PMJoost Klitsie
07/19/2020, 3:08 PMAdam Powell
07/19/2020, 3:09 PMJoost Klitsie
07/19/2020, 3:09 PMJoost Klitsie
07/19/2020, 3:09 PMJoost Klitsie
07/19/2020, 3:10 PMval viewState = stateFlow.collectAsState(Dispatchers.Main.immediate).value
Adam Powell
07/19/2020, 3:10 PMAdam Powell
07/19/2020, 3:11 PMJoost Klitsie
07/19/2020, 3:12 PM@Composable
fun <T : R, R> Flow<T>.collectAsState(
initial: R,
context: CoroutineContext = Dispatchers.Main
): State<R> {
val state = state { initial }
onPreCommit(this, context) {
val job = CoroutineScope(context).launch {
collect {
FrameManager.framed {
state.value = it
}
}
}
onDispose { job.cancel() }
}
return state
}
Joost Klitsie
07/19/2020, 3:13 PMAdam Powell
07/19/2020, 3:14 PMJoost Klitsie
07/19/2020, 3:15 PMAdam Powell
07/19/2020, 3:16 PMJoost Klitsie
07/19/2020, 3:16 PMJoost Klitsie
07/19/2020, 3:16 PMAdam Powell
07/19/2020, 3:16 PMJoost Klitsie
07/19/2020, 3:17 PMJoost Klitsie
07/19/2020, 3:17 PMJoost Klitsie
07/19/2020, 3:17 PMAdam Powell
07/19/2020, 3:38 PMAdam Powell
07/19/2020, 3:39 PMAdam Powell
07/19/2020, 3:40 PMAndroidUiDispatcher
is in dev14 can you try AndroidUiDispatcher.Main
as the context for that collect operation?Joost Klitsie
07/19/2020, 3:40 PMAdam Powell
07/19/2020, 3:40 PMJoost Klitsie
07/19/2020, 3:41 PMJoost Klitsie
07/19/2020, 3:42 PMAdam Powell
07/19/2020, 3:43 PMlaunchInComposition
, which is probably what the collect extensions should be updated to use. (The former didn't exist when the collect extensions were first written)Adam Powell
07/19/2020, 3:44 PMAdam Powell
07/19/2020, 3:45 PMJoost Klitsie
07/19/2020, 3:46 PMAdam Powell
07/19/2020, 3:47 PMAdam Powell
07/19/2020, 3:49 PMAdam Powell
07/19/2020, 3:51 PMJoost Klitsie
07/19/2020, 4:00 PMAdam Powell
07/19/2020, 4:24 PMAdam Powell
07/19/2020, 4:25 PMAdam Powell
07/19/2020, 4:26 PMJoost Klitsie
07/19/2020, 4:26 PMAdam Powell
07/19/2020, 4:26 PMJoost Klitsie
07/19/2020, 4:27 PM