Выполнение синхронных звонков в Cloud Firestore при запуске из основного потока - PullRequest
0 голосов
/ 13 июня 2018

Я создаю приложение на основе версии Kotlin для Android Clean Architecture (https://github.com/android10/Android-CleanArchitecture-Kotlin).

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

abstract class UseCase<out Type, in Params> where Type : Any {

abstract suspend fun run(params: Params): Either<Failure, Type>

fun execute(onResult: (Either<Failure, Type>) -> Unit, params: Params) {
    val job = async(CommonPool) { run(params) }
    launch(UI) { onResult.invoke(job.await()) }
}

В своем примере архитектуры Mr. Android10 использует Retrofit для выполнения синхронного вызова API внутри процедуры kotlin. Например:

override fun movies(): Either<Failure, List<Movie>> {
            return when (networkHandler.isConnected) {
                true -> request(service.movies(), { it.map { it.toMovie() } }, emptyList())
                false, null -> Left(NetworkConnection())
            }
        }

private fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
            return try {
                val response = call.execute()
                when (response.isSuccessful) {
                    true -> Right(transform((response.body() ?: default)))
                    false -> Left(ServerError())
                }
            } catch (exception: Throwable) {
                Left(ServerError())
            }
        }

«Любой» представляет непересекающийся тип, означающий, что результатом будет либо Failure, либо объект типа T, который вы хотите.

Его метод service.movies () реализован таким образом (с использованием модернизации))

@GET(MOVIES) fun movies(): Call<List<MovieEntity>>

Теперь вот мой вопрос . Я заменяю модификацию на Google Cloud Firestore. Я знаю, что в настоящее время Firebase / Firestore является полностью асинхронной библиотекой. Я хочу знать, еслиКто-нибудь знает способ более элегантного способа сделать синхронный вызов API для Firebase.

Я реализовал свою собственную версию Call:

interface Call<T: Any> {
    fun execute(): Response<T>

    data class Response<T>(var isSuccessful: Boolean, var body: T?, var failure: Failure?)
}

и мой вызов API реализован здесь

override fun movieList(): Call<List<MovieEntity>> = object : Call<List<MovieEntity>> {
        override fun execute(): Call.Response<List<MovieEntity>> {
            return movieListResponse()
        }
    }

    private fun movieListResponse(): Call.Response<List<MovieEntity>> {
        var response: Call.Response<List<MovieEntity>>? = null
        FirebaseFirestore.getInstance().collection(DataConfig.databasePath + MOVIES_PATH).get().addOnCompleteListener { task ->
            response = when {
                !task.isSuccessful -> Call.Response(false, null, Failure.ServerError())
                task.result.isEmpty -> Call.Response(false, null, MovieFailure.ListNotAvailable())
                else -> Call.Response(true, task.result.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
            }
        }
        while (response == null)
            Thread.sleep(50)

        return response as Call.Response<List<MovieEntity>>
    }

Конечно, цикл while в конце беспокоит меня. Есть ли другие, более изящные способы, чтобы ждатьответ, который должен быть назначен до возврата из метода movieListResponse?

Я попытался вызвать await () для Задачи, возвращаемой из метода Firebase get(), но метод movieListResponse в любом случае немедленно вернется.Спасибо за помощь!

Ответы [ 3 ]

0 голосов
/ 13 июня 2018

Это слишком сильно, есть несколько слоев, пытающихся сделать то же самое.Я предлагаю вам вернуться на несколько шагов назад, отменить абстракции и войти в настроение непосредственного использования сопрограмм.Реализуйте suspend fun в соответствии с этим шаблоном .Вам не нужны костыли Either, обрабатывайте исключения наиболее естественным образом: try-catch вокруг suspend fun вызова.

В итоге вы должны получить подпись следующим образом:

suspend fun movieList(): List<MovieEntity>

Звоните на сайт:

launch(UI) {
    try {
        val list = movieList()
        ...
    } catch (e: FireException) {
        // handle
    }
}
0 голосов
/ 22 июня 2018

Итак, я нашел то, что искал в API задач Google: «Если ваша программа уже выполняется в фоновом потоке, вы можете заблокировать задачу, чтобы получить результат синхронно и избежать обратных вызовов» https://developers.google.com/android/guides/tasks#blocking

Таким образом, мой предыдущий проблемный код становится:

private fun movieListResponse(): Call.Response<List<MovieEntity>> {
        return try {
            val taskResult = Tasks.await(FirebaseFirestore.getInstance().
                    collection(DataConfig.databasePath + MOVIES_PATH).get(), 2, TimeUnit.SECONDS)
            Call.Response(true, taskResult.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
        } catch (e: ExecutionException) {
            Call.Response(false, null, Failure.ServerError())
        } catch (e: InterruptedException) {
            Call.Response(false, null, Failure.InterruptedError())
        } catch (e: TimeoutException) {
            Call.Response(false, null, Failure.TimeoutError())
        }
    }

Примечание. Мне больше не нужен мой поток Thread.leep while. Этот код следует запускать только в фоновом потоке / сопрограмме kotlin.

0 голосов
/ 13 июня 2018

Это не так, как работает FireBase.Firebase основывается на обратном вызове.

Я рекомендую liveata компонента архитектуры.

Пожалуйста, проверьте следующий пример.

вот ссылка: https://android.jlelse.eu/android-architecture-components-with-firebase-907b7699f6a0

...