AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Kotlin Patterns

skill-piyushverma0-android-agent-skills-kotlin-patterns · by piyushverma0

|

No reviews yet
0 installs
39 views
0.0% view→install

Install

$ agentstack add skill-piyushverma0-android-agent-skills-kotlin-patterns

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-piyushverma0-android-agent-skills-kotlin-patterns)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Kotlin Patterns? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Kotlin Patterns for Android

12 rules for idiomatic, production-safe Kotlin on Android.

Rule 1: Coroutine scope — always use structured concurrency

// ✅ viewModelScope — auto-cancelled when ViewModel is cleared
class MyViewModel : ViewModel() {
    fun load() {
        viewModelScope.launch {
            val result = fetchData()   // suspend, cancellable
        }
    }
}

// ✅ lifecycleScope — tied to Activity/Fragment lifecycle
class MyActivity : ComponentActivity() {
    override fun onStart() {
        super.onStart()
        lifecycleScope.launch {
            viewModel.events.collect { handleEvent(it) }
        }
    }
}

// ❌ GlobalScope — not structured, leaks, not cancellable
GlobalScope.launch { fetchData() }

// ❌ CoroutineScope(Dispatchers.IO) without proper lifecycle binding
val scope = CoroutineScope(Dispatchers.IO)
scope.launch { fetchData() }  // never cancelled

Rule 2: Dispatcher discipline — always switch off Main

// ✅ IO-bound work: switch to IO dispatcher
suspend fun fetchUser(id: String): User = withContext(Dispatchers.IO) {
    api.getUser(id)
}

// ✅ CPU-intensive work: use Default dispatcher
suspend fun processLargeList(items: List): List = withContext(Dispatchers.Default) {
    items.map { processItem(it) }
}

// ✅ Inject dispatcher for testability
class UserRepository @Inject constructor(
    private val api: UserApi,
    @IoDispatcher private val dispatcher: CoroutineDispatcher
) {
    suspend fun getUser(id: String): User = withContext(dispatcher) {
        api.getUser(id)
    }
}

// ❌ Network call on Main thread — crashes with NetworkOnMainThreadException
suspend fun fetchUser(): User = api.getUser()  // on Main, wrong

Rule 3: StateFlow — expose, never expose MutableStateFlow

// ✅ Mutable private, immutable public
private val _uiState = MutableStateFlow(HomeUiState.Loading)
val uiState: StateFlow = _uiState.asStateFlow()

// ✅ Update state correctly
_uiState.value = HomeUiState.Success(items)          // from coroutine on Main
_uiState.update { current -> current.copy(isLoading = false) }  // thread-safe update

// ❌ Exposing MutableStateFlow — external code can change state
val uiState = MutableStateFlow(HomeUiState.Loading)  // anyone can set this

Rule 4: stateIn — convert cold Flow to StateFlow

// ✅ stateIn with correct parameters
val items: StateFlow> = repository.getItemsFlow()
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000),  // 5s timeout before cancelling
        initialValue = emptyList()
    )

// ✅ WhileSubscribed(5000) — keeps upstream alive 5s after last subscriber
// This handles configuration changes without re-fetching

Rule 5: runCatching — safe error handling without try/catch

// ✅ runCatching wraps any exception in Result
suspend fun getItems(): Result> = runCatching {
    api.getItems().map { it.toDomain() }
}

// ✅ Chain Result transformations
suspend fun getActiveItems(): Result> =
    getItems()
        .map { items -> items.filter { it.isActive } }
        .onFailure { error -> logger.e(error, "Failed to get items") }

// ✅ In ViewModel
viewModelScope.launch {
    getItems()
        .onSuccess { items -> _uiState.value = HomeUiState.Success(items) }
        .onFailure { error -> _uiState.value = HomeUiState.Error(error.message ?: "Error") }
}

// ❌ Try/catch scattered through ViewModel — inconsistent, hard to compose
try {
    val items = api.getItems()
    _uiState.value = HomeUiState.Success(items)
} catch (e: Exception) {
    _uiState.value = HomeUiState.Error(e.message ?: "Error")
}

Rule 6: Sealed interfaces — prefer over sealed classes for state/events

// ✅ sealed interface — no constructor, lighter, more composable
sealed interface LoginResult {
    data object Success : LoginResult
    data class Error(val code: Int, val message: String) : LoginResult
    data object NetworkError : LoginResult
}

// ✅ sealed interface allows a class to implement multiple sealed hierarchies
class AuthError : LoginResult.Error(401, "Unauthorized"), ProfileResult.Unauthorized

// ❌ sealed class — requires a superclass constructor
sealed class LoginResult {
    object Success : LoginResult()
    data class Error(val message: String) : LoginResult()
}

Rule 7: Scope functions — use the right one

// ✅ let — transform nullable value, chain operations
val length = name?.let { it.trim().length } ?: 0

// ✅ apply — configure an object, return the object
val intent = Intent(context, MainActivity::class.java).apply {
    putExtra("id", itemId)
    flags = Intent.FLAG_ACTIVITY_NEW_TASK
}

// ✅ also — side effects, return the same object
val items = repository.getItems().also { logger.d("Loaded ${it.size} items") }

// ✅ run — execute block, return result (combine let + with)
val message = user.run {
    if (isPremium) "Welcome, Premium $name!" else "Welcome, $name!"
}

// ✅ with — call multiple methods on an object, return result
val summary = with(order) {
    "Order #$id: $itemCount items, total: $$total"
}

// ❌ Nested let/run — unreadable, use named functions instead
val result = a?.let { b?.let { c?.run { ... } } }  // pyramid of doom

Rule 8: No !! operator in production code

// ✅ Safe alternatives to !!
val name = user?.name ?: "Anonymous"          // Elvis operator
val id = savedStateHandle.get("id") ?: return  // early return
val file = getFile() ?: throw IllegalStateException("File required")  // explicit throw

// ✅ requireNotNull with message
val config = requireNotNull(buildConfig) { "BuildConfig must be initialized before use" }

// ❌ !! crashes with NullPointerException — never use in production
val name = user!!.name   // NPE if user is null
val id = args!!.getString("id")  // NPE in production

Rule 9: flatMapLatest — cancel previous on new emission

// ✅ flatMapLatest cancels in-flight request when new search query arrives
val searchResults: StateFlow> = searchQuery
    .debounce(300L)
    .flatMapLatest { query ->
        if (query.isBlank()) flowOf(emptyList())
        else repository.search(query)
    }
    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

// ❌ flatMapMerge — all emissions run concurrently, results arrive out of order
val searchResults = searchQuery.flatMapMerge { repository.search(it) }

Rule 10: Data classes — correct usage

// ✅ data class for value objects with meaningful equality
data class Item(
    val id: String,
    val title: String,
    val description: String,
    val createdAt: Instant
)

// ✅ copy() for immutable updates
val updated = item.copy(title = "New Title", description = "Updated")

// ✅ data object for singletons in sealed hierarchies
sealed interface AuthState {
    data object Unauthenticated : AuthState
    data object Loading : AuthState
    data class Authenticated(val user: User) : AuthState
}

// ❌ Regular class for domain models — loses equals/hashCode/copy
class Item(val id: String, val title: String)  // no equals, no copy

// ❌ Mutable data class — breaks StateFlow comparison, causes missed updates
data class UiState(var isLoading: Boolean = false)  // var in data class

Rule 11: Lazy initialization

// ✅ by lazy — compute once, reuse, thread-safe by default
val regex: Regex by lazy { Regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$") }

// ✅ lateinit var — for dependency injection and test setup
@Inject lateinit var repository: ItemRepository

// ✅ lateinit with isInitialized check when needed
if (::repository.isInitialized) repository.close()

// ❌ lazy without thread-safety consideration in concurrent contexts
val cache by lazy(LazyThreadSafetyMode.NONE) { mutableMapOf() }
// LazyThreadSafetyMode.NONE is only safe if accessed from single thread

Rule 12: Extension functions — don't abuse them

// ✅ Extension for adding behavior to existing types
fun String.isValidEmail(): Boolean =
    android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()

fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

fun  Flow.throttleFirst(windowDuration: Long): Flow = flow {
    var lastEmission = 0L
    collect { value ->
        val now = System.currentTimeMillis()
        if (now - lastEmission >= windowDuration) {
            lastEmission = now
            emit(value)
        }
    }
}

// ❌ Extension that should be a UseCase — logic belongs in domain
fun List.filterAndSort(): List {
    // business logic in extension = untestable, not reusable across modules
    return filter { it.isActive }.sortedBy { it.title }
}

Common Mistakes Quick Reference

| ❌ Wrong | ✅ Right | |---|---| | GlobalScope.launch | viewModelScope.launch | | user!!.name | user?.name ?: "default" | | collectAsState() | collectAsStateWithLifecycle() | | MutableStateFlow exposed | _private.asStateFlow() | | Try/catch in ViewModel | runCatching in Repository | | var in data class | val — immutable | | flatMapMerge for search | flatMapLatest | | Computation on Main | withContext(Dispatchers.IO) |

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.