ICanHazStream (me.kartikarora.icanhazstream) is a multi-module Android app that tracks movie and TV streaming availability across platforms like Netflix, Disney+, Prime Video, Apple TV, Stan, and Binge.
In this workshop, you will use Gemini in Android Studio to modernise legacy code, generate Compose UI from wireframes, scaffold multi-module features with Agent Mode, connect external context via GitHub MCP, and automate tests with Studio Journeys and the Android CLI.
Android Studio: Minimum Android Studio Quail 4 (2026.1.4+).
Google Account: Signed in to Android Studio for Gemini access.
JDK: JDK 21+.
The android CLI allows terminal scripts and AI agents to interact directly with Android Studio tools and daemons.
Install via terminal:
macOS (Apple Silicon):
curl -fsSL https://dl.google.com/android/cli/latest/darwin_arm64/install.sh | bash
macOS (Intel):
curl -fsSL https://dl.google.com/android/cli/latest/darwin_x86_64/install.sh | bash
Linux (x86_64):
curl -fsSL https://dl.google.com/android/cli/latest/linux_x86_64/install.sh | bash
Windows (cmd):
curl -fsSL https://dl.google.com/android/cli/latest/windows_x86_64/install.cmd -o "%TEMP%\install-android.cmd" && "%TEMP%\install-android.cmd"
Verify installation:
android --version
Install the brand skill so Gemini reuses existing :core:ui components (MovieCard, ProviderBadge) instead of generating generic composables:
npx skills install https://distribute.kartikarora.me/ai/kartikarora-compose-theme.skill
Grant appropriate file permissions for Gemini and Agent Mode to read and scaffold files in the multi-module project:
Open Settings (Cmd+, on macOS / Ctrl+Alt+S on Windows & Linux).
Navigate to Tools > AI > Agent permissions.
Under File Permissions, configure the following options:
Read files in the project: Set to Always allow.
Write source files in the project: Set to Always allow (allows Agent Mode to scaffold composables, ViewModels, and test classes).
Delete or rename files in the project: Set to Always allow (for seamless code refactoring).
Access gitignored files: Keep at Ask every time (sensitive credentials and keys remain protected).
Enable natural language user journey testing with automated multimodal vision assertions:
In Settings, navigate to Studio Labs in the sidebar.
Check Journeys to activate the Studio Journeys testing engine.
Click Apply & OK.
Open the dedicated Agent tool window via View > Tool Windows > Agent (or click the Agent icon in the right sidebar). This window is your primary assistant interface for conversational coding, multi-module feature scaffolding, and MCP tool execution.
Clone the starter repository, establish engineering conventions in AGENTS.md, and block sensitive files with .aiexclude.
Clone the workshop repository from GitHub:
macOS / Linux:
git clone https://github.com/kartikarora/ICanHazStream.git
cd ICanHazStream
Windows (cmd / PowerShell):
git clone https://github.com/kartikarora/ICanHazStream.git
cd ICanHazStream
Open ICanHazStream in Android Studio Quail 4 (2026.1.4+) using File > Open and let the Gradle sync complete. Key modules include:
:app: Navigation graph (StreamNavGraph.kt).
:feature:explore: Trending movies and provider discovery.
:feature:detail: Movie details and "Where to Watch" availability.
:feature:watchlist: Saved watchlist and price drop alerts.
:core:ui: Ready-made design tokens and components (MovieCard, ProviderBadge).
:core:data: Repositories and Ktor Client 3.5.2 networking.
:core:model: @Serializable domain models (Movie, StreamingProvider).
:core:testing: In-memory test fakes (FakeMovieRepository).
Create AGENTS.md in the project root by running this terminal command:
cat << 'EOF' > AGENTS.md
# ICanHazStream — AI Agent Rules
This document defines the rules and conventions for AI coding assistants working in this project.
## Architecture
- **Multi-module project** using convention plugins from `build-logic/`.
- **Package namespace:** `me.kartikarora.icanhazstream.*`
- **Source convention:** Kotlin files in `src/main/kotlin/`, Java files in `src/main/java/`.
## Brand Design System
The `:core:ui` module contains the **@kartikarora Compose Design System** with:
- `ICanHazStreamTheme` — Material 3 theme with Space Grotesk typography
- `MovieCard` — Brand-styled movie card component
- `ProviderBadge` — Streaming platform badge (Netflix, Disney+, Prime Video, Stan, Binge)
- `RatingChip` — Movie rating display chip
- `StreamTopBar` — Top app bar with brand typography
**Always use these components instead of creating raw Composables.**
## Installed AI Skill
The `kartikarora-compose-theme` skill (installed in `.agents/skills/`) teaches the AI
about the pre-built `:core:ui` component library. When generating UI code, always import
`me.kartikarora.icanhazstream.ui.components.*` and `me.kartikarora.icanhazstream.ui.theme.*`.
## Testing Philosophy
- **Fakes over Mocks**: Use test fakes from `:core:testing` (e.g., `FakeMovieRepository`).
- **JUnit 5** for unit tests, **Turbine** for `StateFlow` testing.
- **Compose UI Test** for instrumented tests.
## JetBrains Stack
- **Ktor Client 3.5.2** for HTTP networking
- **kotlinx.serialization 1.11.0** for JSON parsing
- **kotlinx.coroutines 1.11.0** for async operations
EOF
Create .aiexclude in the project root to prevent API keys and credentials from being indexed or sent to cloud models:
cat << 'EOF' > .aiexclude
# Block local keystores, private credentials, and solution code from Gemini indexing
*.jks
*.keystore
local.properties
google-services.json
.solutions/
EOF
Refactor code using specific architecture guidelines and review live diffs in the editor.
Open TrendingMoviesViewModel.kt in the :feature:explore module:
TrendingMoviesViewModel.kt
private val _trendingMovies = MutableLiveData<List<Movie>>()
val trendingMovies: LiveData<List<Movie>> = _trendingMovies
Open the Agent tool window (View > Tool Windows > Agent or from the right sidebar) and submit your refactoring prompt:
@TrendingMoviesViewModel.kt Refactor the _trendingMovies LiveData stream to StateFlow with an initial empty list, and expose an immutable asStateFlow().
Review the side-by-side diff preview and click Apply Changes (Cmd+Enter or click Apply).
TrendingMoviesViewModel.kt
private val _trendingMovies = MutableStateFlow<List<Movie>>(emptyList())
val trendingMovies: StateFlow<List<Movie>> = _trendingMovies.asStateFlow()
Convert legacy Java utilities into idiomatic Kotlin functions.
Open WatchCostUtils.java in the :core:data module:
WatchCostUtils.java
package me.kartikarora.icanhazstream.data.legacy;
public class WatchCostUtils {
public static double calculateOptimalWatchCost(double monthlySubscription, double rentPrice, int expectedViews) {
if (monthlySubscription <= 0.0 || rentPrice <= 0.0 || expectedViews <= 0) {
return 0.0;
}
double totalRentalCost = rentPrice * expectedViews;
return Math.min(monthlySubscription, totalRentalCost);
}
}
Open the Agent tool window (View > Tool Windows > Agent) and submit:
@WatchCostUtils.java Convert this Java utility class to an idiomatic Kotlin file with a top-level calculation function in package me.kartikarora.icanhazstream.data.
Save the output to WatchCostUtils.kt in the :core:data module and delete the old .java file.
WatchCostUtils.kt
package me.kartikarora.icanhazstream.data
fun calculateOptimalWatchCost(
monthlySubscription: Double,
rentPrice: Double,
expectedViews: Int
): Double {
if (monthlySubscription <= 0.0 || rentPrice <= 0.0 || expectedViews <= 0) return 0.0
val totalRentalCost = rentPrice * expectedViews
return totalRentalCost.coerceAtMost(monthlySubscription)
}
Migrate an XML layout and ViewHolder to a declarative @Composable using brand design tokens.
Open item_movie_provider.xml and MovieProviderCard.kt in the :feature:explore module:
MovieProviderCard.kt
package me.kartikarora.icanhazstream.explore
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import me.kartikarora.icanhazstream.model.StreamingProvider
@Composable
fun MovieProviderCard(
provider: StreamingProvider,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
}
Open the Agent tool window (View > Tool Windows > Agent or from the right sidebar) and enter:
@item_movie_provider.xml Convert this XML layout and its ViewHolder into a declarative Jetpack Compose composable for MovieProviderCard.kt. Use the @kartikarora design tokens, MovieCard, and ProviderBadge components from :core:ui.
MovieProviderCard.kt
package me.kartikarora.icanhazstream.explore
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import me.kartikarora.icanhazstream.model.StreamingProvider
import me.kartikarora.icanhazstream.ui.components.MovieCard
import me.kartikarora.icanhazstream.ui.components.ProviderBadge
@Composable
fun MovieProviderCard(
provider: StreamingProvider,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
MovieCard(
onClick = onClick,
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = provider.name,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Quality: ${provider.quality} • Plan: ${provider.planType}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
ProviderBadge(text = provider.quality)
}
}
}
Speed up daily tasks with built-in documentation, code explanation, and commit helpers.
Open Movie.kt in the :core:model module. Open the Agent tool window (View > Tool Windows > Agent) and submit this prompt:
@Movie.kt Generate comprehensive KDoc comments for this data class and all its properties.
Movie.kt
package me.kartikarora.icanhazstream.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Represents a movie or TV title available across streaming services.
*
* @property id Unique TMDB identifier.
* @property title The official release title.
* @property overview Synopsis and storyline summary.
* @property posterPath Relative poster image path from TMDB.
* @property releaseDate The theatrical or digital release date (YYYY-MM-DD format).
* @property voteAverage Average user rating score out of 10.
* @property providers List of streaming providers and purchase options for this movie.
*/
@Serializable
data class Movie(
val id: String,
val title: String,
val overview: String,
@SerialName("poster_path") val posterPath: String? = null,
@SerialName("release_date") val releaseDate: String? = null,
@SerialName("vote_average") val voteAverage: Double = 0.0,
val providers: List<StreamingProvider> = emptyList(),
)
Open WatchOption.kt in the :core:model module. Notice the deep-link validation logic:
WatchOption.kt
fun isValidDeepLink(): Boolean {
val urlPattern = Regex("""^https:\/\/(?:www\.)?(?:netflix|disneyplus|primevideo|stan|binge)\.com(?:\/[a-zA-Z0-9_\-\.\/?%&=]*)?$""")
return deepLinkUrl != null && urlPattern.matches(deepLinkUrl)
}
Highlight the regular expression pattern, right-click, and select AI > Explain Code. Android Studio automatically sends the selection to the Agent tool window and provides an instant breakdown of capture groups, non-capturing groups, and supported streaming domains.
Stage modified files in the Commit tool window (Cmd+K / Ctrl+K) and click Suggest Commit Message:
feat(explore): convert movie provider item layout to Compose and add KDoc
Attach UI wireframe sketches directly into the Agent tool window to generate Compose layouts.
Use assets/wireframe-where-to-watch.png (or the diagram above) and open WhereToWatchScreen.kt in the :feature:detail module.
Attach assets/wireframe-where-to-watch.png in the Agent tool window with this prompt:
Generate the Jetpack Compose screen for WhereToWatchScreen.kt matching this wireframe mockup.
Include:
1. Movie poster header with title, release year, runtime, and 4K UHD badge.
2. Streaming subscription provider cards (Netflix, Disney+).
3. Rent and buy options (Apple TV, Google Play).
4. "Add to Watchlist & Alerts" button at the bottom.
Use Material 3 components and design tokens from :core:ui.
Paste the generated composable into WhereToWatchScreen.kt.
WhereToWatchScreen.kt
package me.kartikarora.icanhazstream.detail
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import me.kartikarora.icanhazstream.model.Movie
import me.kartikarora.icanhazstream.model.StreamingProvider
import me.kartikarora.icanhazstream.model.WatchOptionType
import me.kartikarora.icanhazstream.ui.components.ProviderBadge
import me.kartikarora.icanhazstream.ui.components.RatingChip
@Composable
fun WhereToWatchScreen(
movie: Movie?,
onProviderClick: (StreamingProvider) -> Unit,
modifier: Modifier = Modifier,
) {
if (movie == null) return
var selectedTabIndex by remember { mutableIntStateOf(0) }
val tabs = listOf("Stream", "Rent", "Buy")
val currentType = when (selectedTabIndex) {
0 -> WatchOptionType.STREAM
1 -> WatchOptionType.RENT
else -> WatchOptionType.BUY
}
val filteredProviders = movie.providers.filter { it.type == currentType }
LazyColumn(
modifier = modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
item {
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(text = movie.title, style = MaterialTheme.typography.headlineMedium)
RatingChip(rating = movie.voteAverage)
}
Spacer(modifier = Modifier.height(8.dp))
Text(text = movie.overview, style = MaterialTheme.typography.bodyLarge)
}
}
}
item {
TabRow(selectedTabIndex = selectedTabIndex) {
tabs.forEachIndexed { index, title ->
Tab(
selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index },
text = { Text(title) }
)
}
}
}
items(filteredProviders) { provider ->
Card(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
ProviderBadge(text = provider.quality ?: "HD")
Button(onClick = { onProviderClick(provider) }) {
Text("Watch on ${provider.name}")
}
}
}
}
}
}
Iterate on visual styling directly inside the Compose Preview panel using natural language.
Open the Agent tool window (View > Tool Windows > Agent) and submit this prompt:
@WhereToWatchScreen.kt Add a @PreviewLightDark Composable preview function WhereToWatchScreenPreview() wrapped in ICanHazStreamTheme.
@PreviewLightDark
@Composable
private fun WhereToWatchScreenPreview() {
ICanHazStreamTheme {
WhereToWatchScreen(
movieId = "inception-2010",
onNavigateBack = {}
)
}
}
Build the project (Cmd+F9 / Ctrl+F9) to render the preview.
In the Compose Preview toolbar, click the AI icon and select Change UI (under For Selected Preview).
Prompt:
Set provider card corner radius to 16dp, add 4K HDR badges next to streaming platforms, and style the 'Add to Watchlist' button with primary container styling.
Connect Gemini to the GitHub MCP Server to ground code generation in repository issues, PRDs, and pull requests.
Add the GitHub MCP server to .gemini/mcp.json (or via Settings > Tools > AI > MCP Servers):
.gemini/mcp.json
{
"mcpServers": {
"github/github-mcp-server": {
"httpUrl": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}"
},
"timeout": -1,
"enabled": true,
"trust": false,
"includeTools": [],
"excludeTools": []
}
}
}
In the Agent tool window, enter:
Using the connected GitHub MCP server, fetch the product requirements and accepted schema from issue #1 in kartikarora/ICanHazStream ('Watchlist price drop alerts and regional availability notifications'). Ground the implementation in our existing data layer.
Gemini queries get_issue and get_file_contents over MCP to retrieve the exact requirements before scaffolding.
Use Agent Mode to plan, write, and link features across multiple modules autonomously.
In the Agent tool window (View > Tool Windows > Agent), submit your prompt to build the feature across modules:
Build the "Watchlist & Price Drop Alerts" feature in :feature:watchlist.
1. Create WatchlistViewModel.kt exposing WatchlistUiState (Loading, Empty, Success).
2. Create WatchlistRepository.kt with functions to add movies, track rental price drops, and observe saved titles.
3. Build WatchlistScreen.kt with movie cards, price alert toggles, and swipe-to-delete.
4. Add the watchlist route to the root StreamNavGraph.kt in :app.
Follow the rules in AGENTS.md and use Ktor 3.5.2 and StateFlow.
Agent Mode creates WatchlistViewModel.kt, WatchlistRepository.kt, and WatchlistScreen.kt in the :feature:watchlist module, and registers the route in StreamNavGraph.kt in the :app module.
Review the structured multi-file diff and click Apply All Changes.
Let Agent Mode run Gradle build tasks and fix missing dependencies automatically.
In the Agent tool window, submit:
Check and compile the project by running a Gradle build for :feature:watchlist. If any dependencies or imports are missing in build.gradle.kts or libs.versions.toml, diagnose and fix them.
Agent Mode:
Executes ./gradlew :feature:watchlist:assembleDebug.
Spots missing test dependencies (runTest, turbine).
Updates build.gradle.kts in the :feature:watchlist module and syncs Gradle.
Re-runs the build until compilation succeeds.
Generate ViewModel unit tests backed by in-memory fakes and Turbine Flow assertions.
Open WatchlistViewModelTest.kt in the :feature:watchlist module.
Open the Agent tool window (View > Tool Windows > Agent) and submit your test prompt:
@WatchlistViewModel.kt Generate unit tests for WatchlistViewModel inside WatchlistViewModelTest.kt using FakeMovieRepository from :core:testing and app.cash.turbine.test. Include test cases for empty state, adding a movie to watchlist, and price drop notifications.
WatchlistViewModelTest.kt
package me.kartikarora.icanhazstream.watchlist
import app.cash.turbine.test
import kotlinx.coroutines.test.runTest
import me.kartikarora.icanhazstream.model.Movie
import me.kartikarora.icanhazstream.testing.FakeMovieRepository
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class WatchlistViewModelTest {
private val fakeRepository = FakeMovieRepository()
private val viewModel = WatchlistViewModel(repository = fakeRepository)
@Test
fun `when movie is added to watchlist, state updates with saved title`() = runTest {
viewModel.uiState.test {
assertEquals(WatchlistUiState.Empty, awaitItem())
val sampleMovie = Movie(
id = "m1",
title = "Inception",
overview = "Dream within a dream",
releaseYear = 2010,
rating = 8.8,
posterUrl = "https://image.tmdb.org/t/p/w500/inception.jpg"
)
viewModel.addToWatchlist(sampleMovie)
val successState = awaitItem() as WatchlistUiState.Success
assertEquals(1, successState.watchlist.size)
assertEquals("Inception", successState.watchlist.first().title)
}
}
}
Run tests (Ctrl+Shift+R / Cmd+Shift+R) to confirm they pass.
Diagnose and patch exceptions directly from Logcat using Ask Gemini.
Run the app on the emulator, open Explore, tap "Untracked Indie Release #9", and set region filter to "Australia (AU)".
Open Logcat (Cmd+6 / Alt+6).
Find the error:
FATAL EXCEPTION: main
java.lang.NullPointerException: Missing streaming provider list for region 'AU' in MovieDetailViewModel.kt:42
Click Ask Gemini next to the stack trace.
Gemini highlights the unhandled null provider list in MovieDetailViewModel.kt:
val providers = movie.regionalProviders[selectedRegion] ?: emptyList()
if (providers.isEmpty()) {
_uiState.value = MovieDetailUiState.NoProvidersAvailable(selectedRegion)
return
}
Apply the change and re-run to verify the fix.
Describe end-to-end user journeys in plain English and execute them on a live emulator using multimodal vision AI.
Open app/src/journeysTest/find_streaming_provider.journey.xml:
find_streaming_provider.journey.xml
<?xml version="1.0" encoding="utf-8"?>
<journey name="find_streaming_provider">
<description>Find streaming provider availability for Dune: Part Two</description>
<actions xml:space="preserve">
<action>View the trending movies list on the Explore screen</action>
<action>Tap on the movie card for "Dune: Part Two"</action>
<action>Switch to the "Stream" tab</action>
<action>Verify Netflix is available at AU$16.99/mo in 4K</action>
<action>Tap "Watch" button to trigger provider launch</action>
</actions>
</journey>
Journeys run against specific build variants configured in your module-level build file. When created with the wizard, the test suite targets the currently active build variant.
If you switch active build variants in Android Studio (for example, to a different product flavor or build type), configure the test suite under testOptions in app/build.gradle.kts:
android {
// ...
testOptions {
suites {
create("journeysTest") {
useJunitEngine {
inputs += listOf(com.android.build.api.dsl.AgpTestSuiteInputParameters.TESTED_APKS)
includeEngines += listOf("journeys-test-engine")
enginesDependencies(libs.junit.platform.launcher)
enginesDependencies(libs.junit.platform.engine)
enginesDependencies(libs.journeys.junit.engine)
}
targetVariants += listOf("debug")
}
}
}
}
Ensure Journeys is enabled under Settings > Studio Labs.
Select an Android Emulator or connected device from the toolbar.
Open app/src/journeysTest/find_streaming_provider.journey.xml in the editor. You can switch between Code view and Design view in the top right.
In Design view, click Run Journey, or in Code view, click the Run icon in the gutter next to the <journey> tag.
Android Studio creates a Journeys Test configuration, builds and deploys the app, connects to Gemini, and executes the journey on the live device.
When complete, inspect the Journeys Test Results panel to see the step breakdown, device screenshots, and Gemini's reasoning for each action.
Use the Android CLI to inspect project structure, find symbols, render previews, inspect UI layouts, and search official documentation headlessly.
Inspect project metadata and verify running Android Studio instances:
# Describe project structure and build artifact paths
android describe
# Check connected Android Studio instances and open projects
android studio check
Find symbol declarations and run static analysis on source files through Android Studio:
# Find the declaration of a symbol across project modules
android studio find-declaration MovieRepository
# Find usages of a symbol in the open project
android studio find-usages WhereToWatchScreen
# Analyze a source file for errors and warnings in Studio
android studio analyze-file feature/detail/src/main/kotlin/me/kartikarora/icanhazstream/detail/WhereToWatchScreen.kt
Render Compose previews directly to an image without opening the editor:
# Render WhereToWatchPreview to a PNG file
android studio render-compose-preview \
feature/detail/src/main/kotlin/me/kartikarora/icanhazstream/detail/WhereToWatchScreen.kt \
WhereToWatchPreview \
--output-image-file=preview.png
Query authoritative Android developer documentation directly from the terminal:
# Search official Android documentation for Jetpack Navigation 3 guides
android docs search "Navigation Compose Type Safety"
Discover and install specialized official Android agent skills to expand AI capabilities:
# List installed and available Android skills (e.g. edge-to-edge, styles, navigation-3)
android skills list
# Search for skills by keyword
android skills find "navigation"
# Install a specific skill
android skills add navigation-3
Review production telemetry from Firebase Crashlytics and Google Play Vitals inside Android Studio with one-click Gemini diagnostics.
Open AQI: Navigate to View > Tool Windows > App Quality Insights.
Review insights: Click Explain Crash with Gemini on recurring crash clusters.
Apply fix: Apply the suggested patch directly to your Kotlin source file.
Guardrails: Ground code generation with AGENTS.md, .aiexclude, and design token skills.
In-Editor AI: Refactor LiveData to StateFlow and explain regex using the Agent tool window and AI > Explain Code.
Modernisation: Convert legacy Java calculation math and XML layouts to Compose.
Design to Code: Generate UI from wireframes and style interactively in Compose Previews.
Context & Tooling: Connect upstream repository context via GitHub MCP.
Agent Mode: Scaffold multi-module features and self-heal Gradle build issues.
Verification: Test StateFlow with Turbine fakes, debug crashes in Logcat, and automate E2E testing with Studio Journeys and the Android CLI.
On-device AI: Experiment with Gemini Nano via AICore using the ML Kit GenAI Prompt API.
Prompt Library: Store reusable team prompts in .idea/project.prompts.xml.
Questions & Feedback: Reach out at hello@kartikarora.me.