У вас может быть хранилище, открывающее функции suspend
, которые обрабатывают ввод / вывод. Вот TextRepository
, который читает и записывает текст из Uri
(поскольку он адаптирован из будущего учебника, который поддерживает файлы и значения Storage Access Framework Uri
):
class TextRepository(context: Context) {
private val resolver: ContentResolver = context.contentResolver
suspend fun read(source: Uri) = withContext(Dispatchers.IO) {
try {
resolver.openInputStream(source)?.use { stream ->
StreamResult.Content(source, stream.readText())
} ?: throw IllegalStateException("could not open $source")
} catch (e: FileNotFoundException) {
StreamResult.Content(source, "")
} catch (t: Throwable) {
StreamResult.Error(t)
}
}
suspend fun write(source: Uri, text: String): StreamResult =
withContext(Dispatchers.IO) {
try {
resolver.openOutputStream(source)?.use { stream ->
stream.writeText(text)
StreamResult.Content(source, text)
} ?: throw IllegalStateException("could not open $source")
} catch (t: Throwable) {
StreamResult.Error(t)
}
}
}
private fun InputStream.readText(charset: Charset = Charsets.UTF_8): String =
readBytes().toString(charset)
private fun OutputStream.writeText(
text: String,
charset: Charset = Charsets.UTF_8
): Unit = write(text.toByteArray(charset))
sealed class StreamResult {
object Loading : StreamResult()
data class Content(val source: Uri, val text: String) : StreamResult()
data class Error(val throwable: Throwable) : StreamResult()
}
В этом случае я использую шаблон загрузки содержимого ошибки (LCE), где функции suspend
возвращают StreamResult
. StreamResult.Content
переносит прочитанный текст или только что написанный текст.
Тогда у вас может быть ViewModel
своего рода вызов функций suspend
:
class MainMotor(repo: TextRepository) : ViewModel {
private val _results = MutableLiveData<StreamResult>()
val results: LiveData<StreamResult> = _results
fun read(source: Uri) {
_results.value = StreamResult.Loading
viewModelScope.launch(Dispatchers.Main) {
_results.value = repo.read(source)
}
}
fun write(source: Uri, text: String) {
_results.value = StreamResult.Loading
viewModelScope.launch(Dispatchers.Main) {
_results.value = repo.write(source, text)
}
}
}
В моем случае я направляю StreamResult
без изменений через MutableLiveData
для использования пользовательским интерфейсом, следуя шаблону в стиле MVI. На практике ViewModel
, вероятно, преобразует результат репо в нечто, более непосредственно используемое пользовательским интерфейсом, и поэтому LiveData
будет другого типа, а ViewModel
выполняет преобразование.