I'm trying to set a TextField initial value readin...
# compose
p
I'm trying to set a TextField initial value reading it from DataStore flow when the viewmodel inits, but something is not working, because after the value is collected from datastore successfully, the TextField doesn't reflect the change and is still an empty string. Can anyone tell me why, please? Inside my ViewModel I have this:
Copy code
var apiUrl by mutableStateOf("")
private set
init {
    viewModelScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
        val apiUrl = dataStoreRepository.readString("api_url").firstOrNull()
        apiUrl?.let { updateApiUrl(it) }
    }
}
fun updateApiUrl(input: String) { apiUrl = input }
and this is the composable which recovers the value of apiUrl from their vm
Copy code
vm: SettingsScreenViewModel = koinViewModel()
OutlinedTextField(
    value = vm.apiUrl,
    onValueChange = { vm.updateApiUrl(it) },
    label = { Text(stringResource(R.string.api_url)) }
)
m
Are you sure you have the same instance of the VM on every recomposition? I don't use koin, but i'm usually using the "by viewModels" syntax for standard compose
p
yes, the same
z
is your viewmodel created in composition? If so, is your datastore returning the new value immediately and setting the MutableState before the composition snapshot is applied?
p
Hi Zach, the viewmodel is created, yes, I checked it with logs, but I don't completly understand you, specially the composition snapshot concept, but I added some logs:
Copy code
Composing composable with projectToken value: 
projectToken collected in vm init block, saving it in mutableState var. Token value: asd9821798asd9a8
Composing composable with projectToken value:
as you can see, first the composable is composed, then, the token is collected and saved, then the composabe is recomposed, but ignoring the value...
@Zach Klippenstein (he/him) [MOD] do you need any more info about this issue?
any info about this?
I can add some info, if I do a
withContext(Dispatchers.Main) {}
embeding
apiUrl?.let { updateApiUrl(it) }
then it works... but why?
z
My guess is still that your background thread is setting the MutableState before the composition snapshot is applied
p
please can you explain me how that can happen and how can I avoid it? I showed my viewmodel and my composable, there is something wrong?
is not correct to load the data on the init block?
z
Yea, don't launch coroutines from init
p
where should I place the loading of the persisted value of my fields?
I thought that viewmodel init block was the perfect place to load persisted values, in fact I remember to see that on the android courses
z
I don't know what the android courses say, but in general it's bad kotlin practice to do side effects in constructors
p
where should I load the persisted data of the screen then?
where and when to be more clear
z
have a separate method on the viewmodel that you call to start the work
p
when and who calls that method?
now that init is not recommended I dont know when and who
z
you could call it from an effect
p
do you mean, adding something like this on the composable?
Copy code
LaunchedEffect(true) {
    vm.loadPersistedData()
}
👍🏻 1
are you sure that doing a LaunchedEffect on the composable for initializing the data is preferable over embeding the part that gives the issue with withContext(Dispatchers.Main) ?
z
Yes, because the real issue is performing side effects in a constructor. That’s a bad practice for multiple reasons, and so it’s better to just avoid doing that entirely.
p
so doing LaunchedEffect(true) { vm.init() } will ensure that will be done only on the first composition and not if the device is rotated or has any configuration change etc...?