• XSS.stack #1 – первый литературный журнал от юзеров форума

Диалог загрузки файлов, Kotlin-Style

tabac

CPU register
Пользователь
Регистрация
30.09.2018
Сообщения
1 610
Решения
1
Реакции
3 332
How to Show Download Progress, Kotlin-Style - небольшая статья с хорошей иллюстрацией современных подходов к разработке на примере диалога загрузки файла.

Когда-то, на заре становления Android как самой популярной платформы, по сети гуляло множество примеров реализации подобной функциональности. Обычно все сводилось к использованию AsyncTask и runOnUiThread(), а сам код выглядел как весьма далекая от паттерна MVC мешанина классов и методов.

Сегодня у нас есть мощные инструменты асинхронного программирования, позволяющие сделать этот код гораздо чище, понятнее и надежнее. И все это с использованием стандартных средств и библиотек разработки.

Итак, для начала создадим sealed-класс для управления состоянием загрузки:
Код:
sealed class DownloadStatus {
    object Success : DownloadStatus()
    data class Error(val message: String) : DownloadStatus()
    data class Progress(val progress: Int): DownloadStatus()
}
Sealed-классы отличаются тем, что могут иметь только ограниченный и заранее определенный набор потомков. Нам нужно всего три состояния: 1. загрузка завершена, 2. ошибка загрузки, 3. текущий прогресс загрузки.

Теперь реализуем саму функцию загрузки:
Код:
suspend fun HttpClient.downloadFile(file: File, url: String): Flow<DownloadStatus> {
    return flow {
        val response = call {
            url(url)
            method = HttpMethod.Get
        }.response
        val byteArray = ByteArray(response.contentLength()!!.toInt())
        var offset = 0
        do {
            val currentRead = response.content.readAvailable(byteArray, offset, byteArray.size)
            offset += currentRead
            val progress = (offset * 100f / byteArray.size).roundToInt()
            emit(DownloadStatus.Progress(progress))
        } while (currentRead > 0)
        response.close()
        if (response.status.isSuccess()) {
            file.writeBytes(byteArray)
            emit(DownloadStatus.Success)
        } else {
            emit(DownloadStatus.Error("File not downloaded"))
        }
    }
}
Это функция-расширение для класса HttpClient Koltin-библиотеки Ktor. Являясь расширением она может использовать методы класса HttpClient напрямую, к тому же нам нет необходимости создавать специальный утилитный класс для нее, в остальном коде она будет выглядеть как метод класса HttpClient.

Кроме того, что это функция-расширение, это еще и suspend-функция, которая возвращает Flow (поток данных), содержащий текущее состояние загрузки. Это значит, что код в теле функции будет выполнен не в момент вызова функции, а в момент получения данных из самого Flow (с помощью метода collect()). Это позволит сделать код более структурированным и понятным.

Наконец, напишем функцию, которая будет запускать загрузку в фоновом потоке и динамически обновлять интерфейс приложения:
Код:
private fun downloadWithFlow() {
    CoroutineScope(Dispatchers.IO).launch {
        ktor.downloadFile(file, url).collect {
            withContext(Dispatchers.Main) {
                when (it) {
                    is DownloadStatus.Success -> {
                        // обновляем UI
                    }
                    is DownloadStatus.Error -> {
                        // обновляем UI
                    }
                    is DownloadStatus.Progress -> {
                        // обновляем UI
                    }
                }
            }
        }
    }
}
Вся логика в одной небольшой функции. Сначала запускаем фоновую корутину (CoroutineScope(Dispatchers.IO).launch), затем запускаем загрузку файла и обновляем UI в соответствии с полученным из Flow состоянием.

@ezobnin
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх