Я не нашел никакой первичной информации об этом или какого-либо хорошего решения, поэтому я придумал свое. Вскоре потребуется преобразовать функции приостановки репозитория в обычные функции с типом возврата настраиваемого интерфейса, который имеет cancel()
функцию-член. Функция примет действие лямбда в качестве параметра. На стороне реализации будет запущена сопрограмма, и ссылка на Job
будет сохранена, поэтому позже, когда потребуется остановить фоновую работу, функция интерфейса cancel()
отменит job
.
Кроме того, поскольку очень трудно прочитать тип ошибки (в случае ее возникновения) из NSError
, я заключил возвращаемые данные в специальный класс, который будет содержать сообщение об ошибке и тип. Ранее я задавал соответствующий вопрос, но не получил хорошего ответа для своего случая, когда ViewModel
изначально написано на каждой платформе.
Если вы обнаружите какие-либо проблемы с этим подходом или у вас есть идеи, поделитесь.
Пользовательская оболочка возвращаемых данных:
class Result<T>(
val status: Status,
val value: T? = null,
val error: KError? = null
)
enum class Status {
SUCCESS, FAIL
}
data class KError(
val type: ErrorType,
val message: String? = null,
)
enum class ErrorType {
UNAUTHORIZED, CANCELED, OTHER
}
Пользовательский интерфейс
interface Cancelable {
fun cancel()
}
Интерфейс репозитория:
//Convert this code inside of Repository interface:
@Throws(Throwable::class)
suspend fun fetchData(): List<String>
//To this:
fun fetchData(action: (Result<List<String>>) -> Unit): Cancelable
Реализация репозитория:
override fun fetchData(action: (Result<List<String>>) -> Unit): Cancelable = runInsideOfCancelableCoroutine {
val result = executeAndHandleExceptions {
val data = networkExample()
// do mapping, db operations, etc.
data
}
action.invoke(result)
}
// example of doing heavy background work
private suspend fun networkExample(): List<String> {
// delay, thread sleep
return listOf("data 1", "data 2", "data 3")
}
// generic function for reuse
private fun runInsideOfCancelableCoroutine(task: suspend () -> Unit): Cancelable {
val job = Job()
CoroutineScope(Dispatchers.Main + job).launch {
ensureActive()
task.invoke()
}
return object : Cancelable {
override fun cancel() {
job.cancel()
}
}
}
// generic function for reuse
private suspend fun <T> executeAndHandleExceptions(action: suspend () -> T?): Result<T> {
return try {
val data = action.invoke()
Result(status = Status.SUCCESS, value = data, error = null)
} catch (t: Throwable) {
Result(status = Status.FAIL, value = null, error = ErrorHandler.getError(t))
}
}
ErrorHandler:
object ErrorHandler {
fun getError(t: Throwable): KError {
when (t) {
is ClientRequestException -> {
try {
when (t.response.status.value) {
401 -> return KError(ErrorType.UNAUTHORIZED)
}
} catch (t: Throwable) {
}
}
is CancellationException -> {
return KError(ErrorType.CANCELED)
}
}
return KError(ErrorType.OTHER, t.stackTraceToString())
}
}
person
Marat
schedule
04.03.2021