← Back to list

Resolving Cognitive Complexity in Android with AI Assistants

This article covers a practical workflow for managing SonarLint’s Cognitive Complexity warnings in Android applications. We will look at…

Esracangungor · 2026-06-13 17:17 · 0 claps · 2.8 min read
#cognitive-complexity #android-app-development #artificial-intelligence #kotlin
Open on Medium ↗
Wiki topics: AI · AI · General GEN · Genomics & Sequencing 📱 · Mobile Development

Resolving Cognitive Complexity in Android with AI Assistants

This article covers a practical workflow for managing SonarLint’s Cognitive Complexity warnings in Android applications. We will look at how to use AI coding assistants to flatten nested logic without breaking your existing ViewModel or UseCase architecture.

First, let’s briefly explain the technical concept and then look at the exact prompts and strategies to refactor heavy classes safely.

What is Cognitive Complexity?

If you’ve spent the better part of the last decade deep in the Android trenches, you know how fast a simple ViewModel function can turn into a massive block of nested if-else statements. SonarLint flags this as Cognitive Complexity. It essentially means your code is too hard for a human brain to trace easily.

Fixing it manually requires deep refactoring, which often gets pushed down the backlog during tight sprint cycles.

The Problem with Blind AI Refactoring

Initially, I tried just highlighting the smelly code and telling the IDE’s AI assistant to “optimize this function.”

The result? The AI got confused and completely rewrote my architecture. It introduced new design patterns I didn’t need, changed method names, and broke my single-source-of-truth UI state management. When you tell an LLM to fix complexity without constraints, it guesses. And in a production Clean Architecture setup, guessing leads to regression bugs.

What are the steps to structure the AI refactor?

To actually fix these warnings safely, you need to isolate the AI’s focus. Treat SonarLint as the diagnostic tool, and the AI as a strictly bounded refactoring engine.

1. The ViewModel State Trap

This is the most common place for complexity to spike. Here is a standard ViewModel function handling multiple user actions and network states.

// SonarLint Warning: Refactor this function to reduce its Cognitive Complexity
fun handleUserAction(action: UserAction) {
    if (action is UserAction.Submit) {
        if (uiState.value.isNetworkAvailable) {
            if (uiState.value.isFormValid) {
                viewModelScope.launch {
                    try {
                        val result = submitFormUseCase(uiState.value.formData)
                        if (result.isSuccessful) {
                            _uiState.value = UiState.Success
                        } else {
                            _uiState.value = UiState.Error(result.errorMessage)
                        }
                    } catch (e: Exception) {
                        _uiState.value = UiState.Error(e.message)
                    }
                }
            } else {
                _uiState.value = UiState.ValidationError
            }
        } else {
            _uiState.value = UiState.NetworkError
        }
    } else if (action is UserAction.Cancel) {
        _uiState.value = UiState.Cancelled
    }
}

Instead of a generic prompt, highlight the block, open your inline chat and use a targeted command:

Prompt: Refactor this function to resolve SonarLint rule S3776. Flatten the nested loops using early returns and when statements. Separate the concerns into private methods. Maintain the exact same UI state mechanism and method signatures.

The refactored output flattens the logic entirely:

fun handleUserAction(action: UserAction) {
    when (action) {
        is UserAction.Submit -> processFormSubmission()
        is UserAction.Cancel -> _uiState.value = UiState.Cancelled
    }
}

private fun processFormSubmission() {
    when {
        !uiState.value.isNetworkAvailable -> _uiState.value = UiState.NetworkError
        !uiState.value.isFormValid -> _uiState.value = UiState.ValidationError
        else -> executeFormSave()
    }
}
private fun executeFormSave() = viewModelScope.launch {
    // Clean coroutine execution here without nested UI state checks
}

2. Refactoring Heavy UseCases

We see the same issue in Clean Architecture UseCases. They are supposed to do one thing, but they often grow into heavy validation gates.

class ProcessPaymentUseCase(private val repository: PaymentRepository) {
    suspend operator fun invoke(paymentData: PaymentData): PaymentResult {
        if (paymentData.amount > 0) {
            if (paymentData.currency == "USD" || paymentData.currency == "EUR") {
                if (paymentData.userToken.isNotEmpty()) {
                    val status = repository.process(paymentData)
                    // Nested status checks...
                }
            }
        }
        return PaymentResult.InvalidConfiguration
    }
}

Provide specific instructions to extract the validation logic:

Prompt: Fix this UseCase according to SonarLint rule S3776. Maintain method signatures and extract the validation logic into a private boolean function to flatten the nested conditionals.

class ProcessPaymentUseCase(private val repository: PaymentRepository) {
    suspend operator fun invoke(paymentData: PaymentData): PaymentResult {
        if (!isValidPaymentRequest(paymentData)) {
            return PaymentResult.InvalidConfiguration
        }
        // Proceed with clean repository processing
    }

private fun isValidPaymentRequest(data: PaymentData): Boolean {
        return data.amount > 0 && 
               data.currency in listOf("USD", "EUR") && 
               data.userToken.isNotEmpty()
    }
}

Final Words

When you explicitly define the boundaries and the specific rule you are trying to fix, the AI tooling becomes genuinely helpful. Before setting up these strict prompts, accepting an AI refactor often meant introducing hidden bugs because it violated separation of concerns.

Now, because the AI knows it is specifically targeting kotlin:S3776 and is restricted from changing the public API, it acts like a precision tool. The less ambiguity you give the model, the more production-ready the refactoring becomes. Just remember: never accept a structural refactoring without running your JVM unit tests immediately after.


메타데이터
post_id
cc44fd55da09
slug
resolving-cognitive-complexity-in-android-with-ai-assistants-cc44fd55da09
url
https://medium.com/@esracangungor/resolving-cognitive-complexity-in-android-with-ai-assistants-cc44fd55da09
canonical_url
https://medium.com/@esracangungor/resolving-cognitive-complexity-in-android-with-ai-assistants-cc44fd55da09
author_url
https://medium.com/@esracangungor
status
ok
fetched_at
2026-06-14 11:28:49