Home > Software engineering >  Android Compose - How to handle ViewModel clear focus event in JetPackCompose?
Android Compose - How to handle ViewModel clear focus event in JetPackCompose?

Time:01-25

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

  • Related