agentsclimarketplace

Kotlin concurrency

Skill thetruong1099/android-mvi-base-code/.claude/skills/kotlin-concurrency

Install
npx -y skills add thetruong1099/android-mvi-base-code --skill kotlin-concurrency

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Kotlin coroutine and Flow patterns for this project. Covers when to use Flow vs suspend in BaseViewModel, Flow operators (map/filter/combine/flatMapLatest/debounce), StateFlow vs Channel vs SharedFlow, Dispatcher selection, structured concurrency with coroutineScope and supervisorScope, and cancellation patterns.

SKILL.md

4.2 KB, as published. Nobody here has run it

Kotlin Concurrency

Coroutine Scopes

ScopeWhereLifecycle
viewModelScopeBaseViewModel subclassesViewModel lifecycle
lifecycleScopeActivity/FragmentLifecycle owner
GlobalScopeNEVER useN/A

Flow vs Suspend in BaseViewModel

Use Flow (non-suspend) for PagingData

private fun loadItems() {
    val result = callPagingDataWithInternet(
        callFlow = { useCase() },
        onError = { showErrorToast(it) },
    ).cachedIn(viewModelScope)
    setState { copy(items = result) }
}

Use suspend for DataState and one-shot operations

// DataState - terminal operation
private fun loadDetail(id: String) {
    viewModelScope.launch {
        collectDataStateWithInternet(
            callFlow = useCase(GetDetailParam(id)),
            onSuccess = { setState { copy(detail = it) } },
            onError = { showErrorToast(it) },
        )
    }
}

// One-shot suspend
private fun saveItem(id: String) {
    viewModelScope.launch {
        callSuspendWithInternet(
            operation = { saveItemUseCase(SaveParam(id)) },
            onSuccess = { showSuccessToast(R.string.saved) },
            onError = { showErrorToast(it) },
        )
    }
}

Flow Operators

// map - transform emissions
repository.getItem(id).map { dto -> mapper.toDomain(dto) }

// combine - merge multiple flows
combine(userFlow, settingsFlow) { user, settings -> UserWithSettings(user, settings) }

// flatMapLatest - cancel previous when new arrives (search)
searchQueryFlow.flatMapLatest { query -> repository.search(query) }

// debounce + distinctUntilChanged - rate limit (search input)
searchQueryFlow
    .debounce(300)
    .distinctUntilChanged()
    .flatMapLatest { repository.search(it) }

// catch - handle errors in flow
dataFlow.catch { e -> emit(DataState.Error(exceptionMapper.mapToAppError(e))) }

// onStart/onCompletion - lifecycle hooks
dataFlow
    .onStart { emit(DataState.Loading()) }
    .onCompletion { onLoading(false) }

StateFlow vs Channel vs SharedFlow

TypeUse caseThis project
StateFlowUI state (always has value)uiState, loadingState, toastState
ChannelOne-time events (consume once)effectFlow
SharedFlowEvents to multiple collectorsNot currently used

Dispatchers

// IO - Network, database, file operations
withContext(Dispatchers.IO) { /* network/db call */ }  // Used in BaseDataSource strategies

// Default - CPU-intensive work
withContext(Dispatchers.Default) { parseHtmlContent(html) }

// Main - UI updates (viewModelScope uses Main by default)

// Tests: MainDispatcherRule replaces Main with TestDispatcher

Structured Concurrency

// Parallel operations
suspend fun loadDashboard() = coroutineScope {
    val items = async { repository.getItems() }
    val categories = async { repository.getCategories() }
    setState { copy(items = items.await(), categories = categories.await()) }
}

// SupervisorScope - one failure doesn't cancel others
supervisorScope {
    launch { syncItems() }     // Can fail independently
    launch { syncUserData() }  // Not affected by syncItems failure
}

Cancellation

// viewModelScope auto-cancels when ViewModel is cleared - no manual cancellation needed

// Manual cancellation for search debounce pattern:
private var searchJob: Job? = null

fun search(query: String) {
    searchJob?.cancel()
    searchJob = viewModelScope.launch {
        collectDataStateWithInternet(...)
    }
}

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.