Reputation: 3356
I have a ViewModel containing a MutableStateFlow called uiState
.
The uiState contains a string value called myString
which is originally initialised from a repository ( remote API), but then should be editable by the user. Whenever the user edits it, it should be saved back to the repository.
class MyViewModel {
private val _uiState = MutableStateFlow(MyUiState())
val uiState: StateFlow<MyUiState> = _uiState.asStateFlow()
}
My understanding is that you should not directly connect a StateFlow value uiState.myString
to a OutlinedTextField. For example, this is clearly stated in this blog post here: https://medium.com/androiddevelopers/effective-state-management-for-textfield-in-compose-d6e5b070fbe5
So what is the correct approach to achieve the desired behaviour?
Here is what I am currently thinking of, but I have no idea if that's a good idea:
Inside the Compose function, define a local string variable that is originally initialised by the value of uiState.myString
, and then, when the input is complete, update first the repository and then uiState.myString
.
@Composable
fun MyTextEdit(
initialValue: String // this arguments gets passed `uiState.myString` when
onKeyboardDone: () -> Unit,
) {
val editString: String = remember { mutableStateOf(default= initialValue) }
OutlinedTextField(
value = editString,
onValueChange = { newValue => editString = newValue}
keyboardActions = KeyboardActions(
onDone = { saveToRegistryAndThenUpdateMyStringInUiState() }
)
[...]
// And then the composable is called like this:
@Composable
fun MyUi(
val uiState by MyViewModel.uiState.collectAsState()
MyTextEdit(
initialValue=uiState.myString,
onKeyboardDone = { newValue ->
saveToRepository(newValue){
savedValue -> uiState.update {
currentState ->
currentState.copy(myString = newValue)
}
}
}
)
Will this work? Is this the best solution?
Upvotes: 1
Views: 704
Reputation: 2214
Its better to save newValue
after some delay in repository whenever user inputs value instead of depending on keyboard's onDone
action.
For delay
- you can use debounce
Here is basic example:
ViewModel
const val DEBOUNCE_DELAY = 600L
val newValue = MutableStateFlow("")
init {
// This will be called at delay of 600MS whenever `newValue` is updated
viewModelScope.launch {
newValue.debounce {
if (it.isEmpty()) 0 else DEBOUNCE_DELAY
}.flowOn(appDispatcher.IO)
.collectLatest { newStr ->
if (newStr.isNotEmpty()) {
withContext(Dispatcher.IO) {
// Save newValue to repository & state
}
}
}
}
}
}
fun onValueChange(newStr: String) {
newValue.value = newStr
}
In View
@Composable
fun MyTextEdit(
initialValue: String // this arguments gets passed `uiState.myString` when
onKeyboardDone: () -> Unit,
) {
val editString: String = remember { mutableStateOf(default= initialValue) }
OutlinedTextField(
value = editString,
onValueChange = { newValue => MyViewModel.onValueChanged(newValue) }
)
[...]
Upvotes: 0
Reputation: 3242
For the best TextField
Best practices you can directly use mutableStateOf
inside the ViewModel
and ViewModel will keep track of it when configuration changes.
You can define a Text variable inside the ViewModel and get it Inside the screen.
Make a function which will update the String value in ViewModel.
Stack07ViewModel.kt
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
class Stack07ViewModel : ViewModel() {
var textFieldValue by mutableStateOf("")
private set
fun updateTextField(str: String) {
textFieldValue = str
}
}
You can use in your screen as below
We are passing the initial value from the
ViewModel
and wheneverTextField
value changes we callupdateTextField
function inViewModel
.
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.md.newcomposeplayground.stackoverflowq.viewmodels.Stack07ViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Stack07(stack07ViewModel: Stack07ViewModel) {
Scaffold (
topBar = {
TopAppBar(title = {
Text(text = "Text Field Demo")
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary
))
}
){
Column (modifier = Modifier
.padding(it)
.fillMaxSize()
.padding(10.dp)){
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = stack07ViewModel.textFieldValue,
onValueChange = {
stack07ViewModel.updateTextField(it)
},
placeholder = {
Text(text = "Enter your text")
}
)
}
}
}
Upvotes: 0