简体   繁体   中英

Kotlin KMM stop coroutine flow with infinite loop properly


I'm building a KMM app for retrieving news. My app fetches news every 30 seconds and save it in a local database. User must be logged for use it. When user want to logout i need to stop refreshing news and delete the local database.
How do i stop a flow with an infinite loop properly without use static variabile?

I designed the app like follows:

  • ViewModel (separate for Android and iOS)
  • UseCase (shared)
  • Repository (shared)
  • Data source (shared)
  • Android Jetpack compose single activity
  • iOS SwiftUI

Android ViewModel:(iOS use ObservableObject, but logic is the same)
 @HiltViewModel class NewsViewModel @Inject constructor( private val startFetchingNews: GetNewsUseCase, private val stopFetchingNews: StopGettingNewsUseCase, ): ViewModel() { private val _mutableNewsUiState = MutableStateFlow(NewsState()) val newsUiState: StateFlow<NewsState> get() = _mutableNewsUiState.asStateFlow() fun onTriggerEvent(action: MapEvents) { when (action) { is NewsEvent.GetNews -> { getNews() } is MapEvents.StopNews -> { //???? } else -> { } } } private fun getNews()() { startFetchingNews().collectCommon(viewModelScope) { result -> when { result.error -> { //update state } result.succeeded -> { //update state } } } } }

UseCase:
 class GetNewsUseCase( private val newsRepo: NewsRepoInterface) { companion object { private val UPDATE_INTERVAL = 30.seconds } operator fun invoke(): CommonFlow<Result<List<News>>> = flow { while (true) { emit(Result.loading()) val result = newsRepo.getNews() if (result.succeeded) { // emit result } else { //emit error } delay(UPDATE_INTERVAL) } }.asCommonFlow() }

Repository:
 class NewsRepository( private val sourceNews: SourceNews, private val cacheNews: CacheNews): NewsRepoInterface { override suspend fun getNews(): Result<List<News>> { val news = sourceNews.fetchNews() //..... cacheNews.insert(news) //could be a lot of news return Result.data(cacheNews.selectAll()) } }


Flow extension functions:
 fun <T> Flow<T>.asCommonFlow(): CommonFlow<T> = CommonFlow(this) class CommonFlow<T>(private val origin: Flow<T>): Flow<T> by origin { fun collectCommon( coroutineScope: CoroutineScope? = null, // 'viewModelScope' on Android and 'nil' on iOS callback: (T) -> Unit, // callback on each emission ) { onEach { callback(it) }.launchIn(coroutineScope?: CoroutineScope(Dispatchers.Main)) } }

I tried to move the while loop inside repository, so maybe i can break the loop with a singleton repository, but then i must change the getNews method to flow and collect inside GetNewsUseCase (so a flow inside another flow).
Thanks for helping!

When you call launchIn on a Flow, it returns a Job. Hang on to a reference to this Job in a property, and you can call cancel() on it when you want to stop collecting it.

I don't see the point of the CommonFlow class. You could simply write collectCommon as an extension function of Flow directly.

fun <T> Flow<T>.collectCommon(
    coroutineScope: CoroutineScope? = null, // 'viewModelScope' on Android and 'nil' on iOS
    callback: (T) -> Unit, // callback on each emission
): Job {
    return onEach {
        callback(it)
    }.launchIn(coroutineScope ?: CoroutineScope(Dispatchers.Main))
}

// ...

   private var fetchNewsJob: Job? = null

   private fun getNews()() {
       fetchNewsJob = startFetchingNews().collectCommon(viewModelScope) { result ->
           when {
               result.error -> {
                //update state
               }
               result.succeeded -> {
                //update state
               }
           }
       }
   }

In my opinion, collectCommon should be eliminated entirely because all it does is obfuscate your code a little bit. It saves only one line of code at the expense of clarity. It's kind of an antipattern to create a CoroutineScope whose reference you do not keep so you can manage the coroutines running in it--might as well use GlobalScope instead to be clear you don't intend to manage the scope lifecycle so it becomes clear you must manually cancel the Job, not just in the case of the news source change, but also when the UI it's associated with goes out of scope.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM