How to handle ViewModel clear focus event in JetPackCompose?
I have a coroutines channel that sometimes notify my screen to clear the TextField
focus
How is the best way to notify my composable to clear focus?
I tried to create a mutableStateFlow, but is there a better way to do it?
@Composable
fun HomeScreen(
viewModel: MainViewModel = hiltViewModel()
) {
val clearFocus by viewModel.clearFocus.collectAsStateWithLifecycle()
AppTheme {
HomeScreenContent(
clearFocus
)
}
}
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
val clearFocus = MutableStateFlow(false)
init {
viewModelScope.launch {
delay(3000)
clearFocus.value = true
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreenContent(
clearFocus: Boolean
) {
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
var value by rememberSaveable { mutableStateOf("initial value") }
TextField(
value = value,
onValueChange = {
value = it
}
)
if(clearFocus) {
focusManager.clearFocus()
}
}
When a coroutine channel notifies the ViewModel
, I want to clear the TextField
focus, how is the best way to achieve that?
Instead of delegating to the HomeScreenContent
the duty of clearing the focus you could do it in HomeScreen
.
You should not use a stateFlow if you want to do an action that does not affect the compose tree. Instead of using StateFlow use a SharedFlow when you want to trigger an Event.
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
val clearFocusEvent = MutableSharedFlow<Unit>()
init {
viewModelScope.launch {
delay(3000)
clearFocusEvent.emit(Unit)
}
}
}
@Composable
fun HomeScreen(
viewModel: MainViewModel = hiltViewModel()
) {
val focusManager = LocalFocusManager.current
LaunchedEffect(Unit) {
viewModel.clearFocusEvent.collectLatest {
focusManager.clearFocus()
}
}
AppTheme {
HomeScreenContent()
}
}
If you want to have more events between your VM and Composable or just a cleaner code, you can make a sealed interface that will represent the events
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
val homeScreenEvent = MutableSharedFlow<HomeScreenEvent>()
init {
viewModelScope.launch {
delay(3000)
homeScreenEvent.emit(HomeScreenEvent.ClearFocus)
}
}
}
sealed interface HomeScreenEvent {
object ClearFocus: HomeScreenEvent
}
@Composable
fun HomeScreen(
viewModel: MainViewModel = hiltViewModel()
) {
val focusManager = LocalFocusManager.current
LaunchedEffect(Unit) {
viewModel.homeScreenEvent.collectLatest {
when(it) {
HomeScreenEvent.ClearFocus -> focusManager.clearFocus()
}
}
}
AppTheme {
HomeScreenContent()
}
}
Now when you'll add an event you just have to handle the new case in the when