Царство с сопрограммой - PullRequest
       70

Царство с сопрограммой

0 голосов
/ 14 февраля 2020

Я использую шаблон MVVM (ViewModel, LiveData, Coroutines) с базой данных Realm. У меня есть метод в моем классе Дао, как показано ниже. Здесь царство вводится Dagger.

fun observeChannels(): LiveRealmData<Channel> {
    val channelResults = realm.where(Channel::class.java).findAll()
    return channelResults.asLiveData()
} 

Это вызывается из репозитория / in, как показано ниже. Таким образом, эта сопрограмма работает в другом потоке, чем та, в которой было создано Царство

suspend operator fun invoke(currentFiltering: ChannelFilterType = ChannelFilterType.ALL_CHANNELS)
        : LiveData<Result<List<ChatChannel>>> = withContext(dispatcher) {

    chatRepository.observeChannels().map {

        if (it is Result.Success && currentFiltering != ChannelFilterType.ALL_CHANNELS) {
            val channelToDisplay = mutableListOf<ChatChannel>()

            for (channel in it.data) {

                when (currentFiltering) {
                    ChannelFilterType.ALL_CHANNELS -> if (!channel.favMembers.contains("")) {
                        channelToDisplay.add(channel)
                    }
                    ChannelFilterType.FAVORITE_CHANNELS -> if (channel.favMembers.contains("")) {
                        channelToDisplay.add(channel)
                    }
                }
            }

            Result.Success(channelToDisplay)
        } else if (it is Result.Error) {
            it
        } else {
            it
        }
    }
}

Так что я получаю это исключение. Доступ к области из неверного потока. Доступ к объектам области возможен только в том потоке, в котором они были созданы.

Как лучше всего решить эту проблему?

1 Ответ

0 голосов
/ 14 февраля 2020

Я не использую Realm, так что я могу ошибаться, что-то здесь, но, просматривая их вводное руководство, там говорится об инициализации Realm в onCreate (Realm.init(this)). Это означает, что он ожидает взаимодействия с основным потоком пользовательского интерфейса. Это означает, что вы должны использовать только Dispatchers.Main, и, пока вы запускаете свои сопрограммы из основной области, такой как lifecycleScope или viewModelScope, вам не нужно указывать диспетчеров или вообще использовать withContext. Методы Realm, которые имеют аргумент обратного вызова, - это то, где он будет делать что-то в фоновом потоке, но вы никогда не должны взаимодействовать с ним в фоновом потоке.

В вашем примере выше, похоже, что вы просто регистрация наблюдателей в вашей функции invoke. Регистрация наблюдателей не является блокирующим вызовом, поэтому вам не следует использовать для нее функцию приостановки или пытаться сделать это в фоновом режиме с помощью withContext().

Область, в которой сопрограммы имеют смысл, - это когда есть некоторые блокирующие действия выполняются в фоновом режиме. Из руководства видно, что Realm предлагает асинхронные транзакции. Похоже, что Realm пока не поддерживает сопрограммы, но вы можете преобразовать методы asyn c в функции приостановки следующим образом:

/** @return `null` on success or a Throwable on failure. */
suspend fun Realm.executeTransactionSuspending(transaction: Realm.Transaction): Throwable? =
    suspendCoroutine { continuation ->
        executeTransactionAsync(
            transaction,
            { continuation.resume(null) },
            { throwable -> continuation.resume(throwable) }
        )
    }

Затем вы можете запускать сопрограммы из основного потока, используя viewModelScope ( или lifecycleScope, если вы находитесь в Деятельности / Фрагменте):

fun doSomeTransaction() = viewModelScope.launch {
    val error = realm.executeTransactionSuspending { bgRealm ->
            val user = bgRealm.createObject<User>()
            user.name = "John"
            user.email = "john@corporation.com"
        }
    if (error != null) {
        // handle error
        return@launch
    }
    // Success. Can do subsequent action like updating live data. 
    // If using this directly from an Activity/Fragment (not good MVVM), you could safely update UI
    // here because lifecycleScope will have cancelled it before it gets to this step if the 
    // Activity/Fragment is already gone.
}

Опять же, я не использую Царство. Ничто из вышеперечисленного не проверено. Вы можете искать информацию о превращении обратных вызовов в Kotlin сопрограмм, если вам нужна помощь, чтобы заставить его работать. Вы также можете использовать coroutine.resumeWithException(throwable), если хотите обернуть ваши звонки в try/catch. Я подумал, что имеет смысл возвращать throwable, поскольку Realm делает необязательным даже получение обратного вызова при сбое.

...