How to move cursor to the end of the TextField after screen rotation or system theme change and keep keyboard shown?
Asked Answered
E

1

6

I have a screen with a Textfield as a Search bar which is autofocused when the screen is first displayed. The problem is, after screen rotation or system theme change (Dark mode/light mode), the cursor moves at the beginning of TextField even if the value of TextField is not empty and the keyboard dismisses itself. Please help, I've been searching for two days. Some code samples:

Compose version: 1.3.0 Material 3: 1.0.0

SearchBookScreen.kt

val myViewModel = SearchBookViewModel()

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchBookScreen(
    modifier: Modifier = Modifier,
    navController: NavController,
    viewModel: SearchBookViewModel = myViewModel,
    filters: Map<SearchFilterType, FilterOptions> = FILTERS
) {
    val focusRequester = remember { FocusRequester() } 

val searchInputValue = viewModel.searchInputValue.value
val filtersState = viewModel.filtersState

LaunchedEffect(Unit) { this.coroutineContext.job.invokeOnCompletion { focusRequester.requestFocus() } }

Scaffold(
    topBar = {
        Column {
            TextField(
                singleLine = true,
                placeholder = {
                    Text(text = stringResource(id = R.string.search_bar))
                },
                value = searchInputValue.text,
                modifier = Modifier
                    .fillMaxWidth()
                    .focusRequester(focusRequester)
                    ,
                onValueChange = {
                    viewModel.onEvent(SearchBookEvent.EnteredSearchValue(it))
                },
                leadingIcon = {
                    IconButton(onClick = {
                        viewModel.onEvent(SearchBookEvent.ClearSearchInput)
                        navController.navigateUp()
                    }
                    ) {
                        Icon(
                            imageVector = Icons.Default.ArrowBack,
                            contentDescription = "Go back"
                        )
                    }
                },
                colors = TextFieldDefaults.textFieldColors(
                    unfocusedIndicatorColor = Color.Transparent,
                    focusedIndicatorColor = Color.Transparent
                ),
                trailingIcon = {
                    IconButton(onClick = { viewModel.onEvent(SearchBookEvent.ClearSearchInput) }) {
                        Icon(imageVector = Icons.Default.Clear, contentDescription = "clear")
                    }
                },
            )

SearchBookViewModel.kt

class SearchBookViewModel: ViewModel() {


private val _searchInputValue = mutableStateOf(TextFieldValue("", selection = TextRange.Zero))
val searchInputValue: State<TextFieldValue> = _searchInputValue


 fun onEvent(event: SearchBookEvent) {
        when (event) {
            is SearchBookEvent.EnteredSearchValue -> {
                _searchInputValue.value = searchInputValue.value.copy(text = event.value, selection = TextRange(event.value.length))
            }
        }
    }
Elmaelmajian answered 9/11, 2022 at 3:13 Comment(0)
C
1

For the selection, the issue is here,

value = searchInputValue.text

your'e manipulating text using TextFieldValue but you are just updating the TextField value parameter using ordinary String (TextFieldValue's text property), anything you do with the TextFieldValue instance is not reflecting to your TextField.

I made some modifications in your code by using the State<TextFieldValue>'s delegate

val searchInputValue by viewModel.searchInputValue

and used it as the actual value instead of its text property to your TextField like this.

value = searchInputValue

However you have to modify how it is being updated, so I modify onValueChange as well, I only used .text as I don't want to modify other parts further and to leave it for you to change it.

 onValueChange = {
    viewModel.onEvent(SearchBookEvent.EnteredSearchValue(it.text))
 }

Your TextField implementation should look like this

TextField(
      singleLine = true,
      placeholder = {
            Text(text = "Type Here")
      },
      value = searchInputValue, // <- notice this
      modifier = Modifier
            .fillMaxWidth()
            .focusRequester(focusRequester),
      onValueChange = {  
             viewModel.onEvent(SearchBookEvent.EnteredSearchValue(it.text)) // <- and this
      }

Apply all these changes and when you switch theme mode, the cursor/selection will stay at the end.

Now for the keyboard issue, showing it initially, I had to implement something from this post adding a delay prior to requesting focus,

LaunchedEffect(Unit) {
    delay(200)
    focusRequester.requestFocus()
}

and to retain the keyboard while changing theme mode, I had to specify this in my activity.

window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED)
Cutup answered 9/11, 2022 at 5:42 Comment(1)
Thank you so much @z.y, it works !! But instead of passing the text to the view model, I passed the entire TextFieldValue (searchInputValue). When I pass only the text, It produces some kind of infinite logs in LogCatElmaelmajian

© 2022 - 2024 — McMap. All rights reserved.