Как мне отменить запуск сопрограммы внутри withContext? - PullRequest
1 голос
/ 29 мая 2020

У меня есть репозиторий, определенный следующим образом.

class StoryRepository {
    private val firestore = Firebase.firestore

    suspend fun fetchStories(): QuerySnapshot? {
        return try {
            firestore
                .collection("stories")
                .get()
                .await()
        } catch(e: Exception) {
            Log.e("StoryRepository", "Error in fetching Firestore stories: $e")
            null
        }
    }
}

У меня также есть такая модель просмотра.

class HomeViewModel(
    application: Application
) : AndroidViewModel(application) {
    private var viewModelJob = Job()
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
    private val storyRepository = StoryRepository()

    private var _stories = MutableLiveData<List<Story>>()
    val stories: LiveData<List<Story>>
        get() = _stories

    init {
        uiScope.launch {
            getStories()
        }
        uiScope.launch {
            getMetadata()
        }            
    }

    private suspend fun getStories() {
        withContext(Dispatchers.IO) {
            val snapshots = storyRepository.fetchStories()
            // Is this correct?
            if (snapshots == null) {
                cancel(CancellationException("Task is null; local DB not refreshed"))
                return@withContext
            }
            val networkStories = snapshots.toObjects(NetworkStory::class.java)
            val stories = NetworkStoryContainer(networkStories).asDomainModel()
            _stories.postValue(stories)
        }
    }

    suspend fun getMetadata() {
        // Does some other fetching
    }

    override fun onCleared() {
        super.onCleared()
        viewModelJob.cancel()
    }
}

Как видите, иногда StoryRepository().fetchStories() может не работать и вернем null. Если возвращаемое значение - null, я бы не хотел продолжать дальнейшее после проверки того, что snapshots является блоком null. Поэтому я хотел бы отменить эту конкретную сопрограмму (ту, которая запускает getStories(), не отменяя другую сопрограмму (ту, которая запускает getMetadata()). Как мне добиться этого и return -ing от withContext a плохая практика?

1 Ответ

2 голосов
/ 29 мая 2020

Несмотря на то, что ваш подход правильный, вы всегда можете внести некоторые улучшения, чтобы упростить его или сделать его более идиоматичным c (особенно если вас не устраивает собственный код).

Это лишь некоторые предложения которые вы можете принять во внимание:

Вы можете использовать Kotlin Scope Functions, или, более конкретно, функцию let следующим образом:

private suspend fun getStories() = withContext(Dispatchers.IO) {
    storyRepository.fetchStories()?.let { snapshots ->
        val networkStories = snapshots.toObjects(NetworkStory::class.java)
        NetworkStoryContainer(networkStories).asDomainModel()
    } ?: throw CancellationException("Task is null; local DB not refreshed")
}

This way you ' буду возвращать ваши данные или бросать CancellationException if null.

Когда вы работаете с сопрограммами внутри ViewModel, у вас есть CoroutineScope, готовый к использованию, если вы добавите эту зависимость в свой файл gradle:

androidx.lifecycle:lifecycle-viewmodel-ktx:{version}

Таким образом, вы можете использовать viewModelScope для создания ваших сопрограмм, которые будут работать в основном потоке:

init {
    viewModelScope.launch {
        _stories.value = getStories()
    }

    viewModelScope.launch {
        getMetadata()
    }
}

Вы можете забыть об отмене его Job во время onCleared поскольку viewModelScope учитывает жизненный цикл.

Теперь все, что вам осталось сделать, это обработать исключение с помощью блока try-catch или с помощью функции invokeOnCompletion, примененной к Job, возвращаемому launch строитель.

...