I want the data to be saved to a file when the viewmodel is destroyed, here is the code:
@HiltViewModel
class MyViewModel @Inject constructor(@ApplicationContext private val context: Context) :
ViewModel() {
private val _uiState = MutableStateFlow("jack")
val uiState: StateFlow<String> = _uiState.asStateFlow()
@OptIn(ExperimentalSerializationApi::class)
override fun onCleared() {
super.onCleared()
// Writing data to a file
CoroutineScope(IO).launch {
val file = File(context.filesDir, "name.json")
val outputStream = file.outputStream()
outputStream.use {
_uiState.collect { value ->
Json.encodeToStream(value, outputStream)
}
}
}
}
}
structured concurrency
?super.onCleared()
?viewModelScope.launch()
, will the coroutine run successfully?CoroutineScope(IO).launch
is a common anti-pattern, I suggest using GlobalScope.launch(IO)
with @OptIn(DelicateCoroutinesApi::class)
to indicate you are intentionally doing it.collect
called on a SharedFlow or StateFlow never returns.encodeToStream
is not interruptible. If that line of code is reached before the viewModelScope
is cancelled, then it will succeed in writing, but if it doesn't, then it won't.You should change your code as follows to prevent the leak:
@OptIn(ExperimentalSerializationApi::class)
@OptIn(DelicateCoroutinesApi::class)
override fun onCleared() {
super.onCleared()
// Writing data to a file
GlobalScope.launch(IO) {
val file = File(context.filesDir, "name.json")
try {
file.outputStream().use { outputStream ->
Json.encodeToStream(uiState.value, outputStream)
}
} catch(e: IOException) {
Log.e(TAG, "Failed to write ui state", e)
}
}
}