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?
CodePudding user response:
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.
Using a SharedFlow
@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()
}
}
Using a sealed interface as event
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