Android Studio Gemini AI Assistant: Context, Prompts, and Refactoring

Last week, while refactoring state management in a Compose screen, I changed every MutableState in the ViewModel to StateFlow. When I moved the cursor into the Composable function, Gemini completed the entire collectAsStateWithLifecycle() call chain and even added the import automatically. What surprised me more was that the generated API matched the project’s lifecycle 2.8.x dependency instead of pulling in an outdated signature.

That happened because Gemini’s context window was doing real work. It can see much more than the small completion range you notice in a single interaction. In most workflows, we simply do not use that context well enough.

Context window: what Gemini actually sees

Once you understand what goes into each Gemini inference, it becomes easier to explain why completion quality varies so much. Based on a few reverse tests, I break the context into three layers.

Layer 1: the full current file. Gemini does not only see a few lines around the cursor. It sees the whole file’s AST, comments, and import list. If you import kotlinx.coroutines.flow.StateFlow at the top of the file, Gemini will naturally refer to it when writing code near the bottom.

Layer 2: the project semantic index. The IDE maintains a project-level code index in the background. Gemini can query it for:

  • API signatures from dependency libraries, extracted from the Gradle dependency tree
  • Public interfaces of other classes in the project
  • Recent Git changes

I verified this behavior by creating a new UserRepository class, then typing val repo = UserRep in another file. Gemini not only completed the class name, but also suggested the constructor parameters. That was not fuzzy text matching. It was querying the code index.

Layer 3: conversation history and editor action sequence. If you first ask Gemini to generate DataStore read and write methods, then later type val settings = somewhere else, it tends to suggest DataStore-related completion instead of SharedPreferences. This short-term memory window lasts for roughly 10 to 15 interactions.

// If you previously asked Gemini to generate this code:
private val _userFlow = MutableStateFlow<User?>(null)
val userFlow: StateFlow<User?> = _userFlow.asStateFlow()

// Then in another function you write:
fun updateUser(/* cursor here; Gemini will suggest parameter types and assignment logic */)

With all three context layers combined, completion quality ultimately depends on the consistency of the codebase itself. Projects with clear naming conventions and architecture produce much better Gemini results than messy legacy code. I have seen this pattern repeatedly across multiple projects.

Prompt engineering: comments are prompts

I often hear teammates complain that Gemini generated the wrong thing, but fewer people check whether the input signals they gave it were strong enough. In Android Studio, the comments you write are Gemini’s prompt.

Gemini responds most accurately to three kinds of signals.

Signal 1: semantic alignment between function signatures and comments. Spending time on a good function name and KDoc is more efficient than writing a pile of half-finished code and asking Gemini to infer your intent.

/**
 * Load user settings from local DataStore and a remote API.
 * Prefer cached data, trigger a background refresh after a 30-minute timeout,
 * and do not block the UI if refresh fails.
 */
suspend fun getSettings(): Flow<SettingsState> {
    // Leave this empty and let Gemini generate the implementation.
}

Keywords in the comment, such as “DataStore,” “30-minute timeout,” “background refresh,” and “do not block the UI,” can guide Gemini toward a complete implementation with combine, flatMapLatest, and cache timestamp checks.

Signal 2: types first. Define data classes and interfaces before asking Gemini to write implementations. The type system is a strong constraint by itself.

sealed interface SettingsState {
    data object Loading : SettingsState
    data class Success(val settings: UserSettings) : SettingsState
    data class Error(val message: String, val cached: UserSettings?) : SettingsState
}

// Gemini will generate complete when branches based on the sealed interface.

Signal 3: constraint comments. “Do not use GlobalScope,” “use Flow instead of LiveData,” and “keep the existing code style” are negative or preference-based instructions that Gemini understands more accurately than general-purpose models. Android Studio’s Gemini has Android-domain tuning, which generic tools such as GitHub Copilot cannot fully match.

One pitfall: avoid mixing Chinese and English in comments. Gemini’s training data is dominated by English codebases, and English comments produce noticeably better intent recognition. In my tests, writing the same logic in English KDoc improved first-choice code match rate by about 30%.

Multi-file refactoring: boundaries and strategy

Gemini added multi-file refactoring near the end of 2024. In practice, it is much more complicated than single-file completion. A real example makes the tradeoffs clearer.

Scenario: migrate LiveData to StateFlow across 12 ViewModels in a project.

Doing it manually requires changing four areas: ViewModel field declarations, every observe() call site, LiveData assertions in tests, and bindings in Hilt modules. The context menu item Gemini > Modify with Gemini can take on this task:

// In any ViewModel file, select the LiveData-related code and enter:
// "Change this ViewModel and all places that reference it from LiveData to StateFlow.
//  Update test assertions at the same time."

Gemini analyzes the reference chain and produces a modification plan. It does not automatically execute every change. Instead, it shows a diff for each file and requires manual confirmation. This semi-automatic design is the right choice. Cross-file refactors can introduce implicit dependency changes, and fully automatic execution would be too risky.

Pitfalls I have hit:

  • When a ViewModel used MediatorLiveData for merging, Gemini directly replaced it with combine, but the dynamic registration behavior of addSource was lost.
  • When Room returned LiveData<List<T>> and needed to become Flow<List<T>>, Gemini did not remind me to change the DAO return type. That requires cross-module awareness, which is still not reliable.

Practical strategy: treat cross-file refactoring as scaffold generation, not exact replacement. Let Gemini run once and produce candidate changes across all files, then manually review irreversible logic changes. Do not expect one-click completion. Use it as a tool that quickly drafts candidate code and saves 80% of the typing, not as a substitute for review.

Engineering rollout: three recommendations

First, .geminiignore and context configuration pay for themselves. Create a .gemini/context file at the project root and write the project background into it: architecture pattern, naming conventions, and key dependency versions. Gemini reads this context during inference. It is much more effective than repeating the same project description at the start of every conversation.

# .gemini/context
This project uses MVVM + Clean Architecture. The View layer uses Jetpack Compose + Navigation.
ViewModels expose state through StateFlow, and the Repository layer returns Flow<T>.
Dependency injection uses Hilt. The network layer uses Retrofit + Kotlinx Serialization.
Naming conventions: Repository classes end with -Repository, and UseCase classes end with -UseCase.
Tests use MockK + Turbine.

Second, use inline chat for code generation instead of the side panel. The inline chat opened from the code editor with Ctrl + \, or Cmd + \ on Mac, limits context to the selected code block and keeps inference more focused. The side panel is useful for open-ended requirement discussion, but it is not as good for generating concrete code.

Third, treat it as a junior pair-programming partner. Gemini is good at boilerplate, repeated pattern completion, API lookup, and simple refactors. Architecture decisions, concurrency safety analysis, and domain modeling are still your responsibility. My current habit is to ask Gemini for the first draft, then review and correct it myself. It saves typing time, not thinking time.