MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
I explain how I integrate a local MCP server into my Android workflow for instant code suggestions, automated test generation, and seamless CI/CD triggers, showing concrete Kotlin examples and performance gains.
On this page
I ship Android apps faster when my IDE talks to a local MCP server that understands my codebase. The server acts as a contextual brain, turning vague prompts into precise Kotlin snippets, test cases, and Gradle tweaks. After six months of daily use, my build‑time feedback loop shrank from minutes to seconds.
Working alone on 22+ apps means I wear every hat—design, coding, QA, release, and monitoring. Constant context switching kills momentum. I used to:
The result was a fragmented flow where each small task incurred a 2‑5 minute penalty. I needed a tool that could ingest my project’s structure, understand my conventions, and produce ready‑to‑copy artifacts instantly. Enter the MCP (Model Context Protocol) server: a lightweight HTTP service that exposes my codebase as a context source for LLMs like Claude Code.
I run the MCP server inside a Docker container on my development machine. The container indexes the Gradle project, exposes a
/contextdocker-compose.ymlversion: "3.8"
services:
mcp-server:
image: ghcr.io/anthropic/mcp-server:latest
container_name: mcp-android
ports:
- "8000:8000"
volumes:
- ./MyApp:/app:ro
environment:
- MCP_SOURCE_ROOT=/app
- MCP_MODEL_ENDPOINT=http://host.docker.internal:11434/api/generate
- MCP_MAX_TOKENS=2048
restart: unless-stoppedAfter
docker compose up -dhttp://localhost:8000http://localhost:8000/complete[!TIP]
Keep the MCP server’s index refreshed by adding a file watcher that triggerswhenevercodemcp-cli reindexchanges. This ensures the LLM always sees the latest symbols.codesrc/
Writing repetitive Compose screens used to eat up my mornings. Now I highlight a
@Composable“Generate a Compose column that displays a list of
objects with swipe‑to‑delete, using Material 3 andcodeTask.”coderememberLazyListState
The server returns a fully formatted snippet:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TaskList(tasks: List<Task>, onDelete: (Task) -> Unit) {
val listState = rememberLazyListState()
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
items(
items = tasks,
key = { it.id }
) { task ->
TaskRow(
task = task,
onDelete = { onDelete(task) }
)
}
}
}The generated code respects my project’s imports, uses the same
TaskTaskRowBefore MCP, I spent ~4 minutes per screen drafting, looking up modifiers, and fixing typos. After MCP, the same screen takes ~30 seconds: invoke, paste, minor tweak. Over a typical day with 10 UI tweaks, that saves 35 minutes.
Unit testing is essential but tedious. I used to write a basic happy‑path test, then forget boundary conditions. With MCP, I ask for a full test suite for a ViewModel function:
“Write a JUnit5 test class for
that covers success, empty list, network error, and malformed JSON responses, using MockK and coroutines test.”codeTasksViewModel.fetchTasks()
The server spits out:
@ExperimentalCoroutinesApi
class TasksViewModelTest {
private val mockRepository = mockk<TasksRepository>()
private val viewModel = TasksViewModel(mockRepository)
private val testDispatcher = StandardTestDispatcher()
@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
}
@After
fun teardown() {
Dispatchers.resetMain()
}
@Test
fun `fetchTasks success populates UI state`() = runTest {
val sample = listOf(Task(1, "Buy milk"), Task(2, "Walk dog"))
coEvery { mockRepository.fetchTasks() } returns sample
viewModel.fetchTasks()
assertEquals(sample, viewModel.tasks.value)
}
@Test
fun `fetchTasks empty list shows empty state`() = runTest {
coEvery { mockRepository.fetchTasks() } returns emptyList()
viewModel.fetchTasks()
assertTrue(viewModel.tasks.value.isEmpty())
}
@Test
fun `fetchTasks network error shows error state`() = runTest {
coEvery { mockRepository.fetchTasks() } throws IOException("Down")
viewModel.fetchTasks()
assertTrue(viewModel.error.value is IOException)
}
@Test
fun `fetchTasks malformed JSON throws parsing exception`() = runTest {
coEvery { mockRepository.fetchTasks() } throws JsonDecodingException("Expected BEGIN_OBJECT", source = null)
viewModel.fetchTasks()
assertTrue(viewModel.error.value is JsonDecodingException)
}
}The test includes proper coroutine handling, MockK stubbing, and cleanup—exactly what I would have written after consulting the documentation and double‑checking edge cases. I now generate a test file in under a minute versus the 8‑10 minutes I previously spent drafting, reviewing, and debugging.
[!NOTE]
The MCP server can also suggestannotations for new Kotlin DSL testing frameworks (e.g.,code@Test) if you switch thecodekotlin.testflag in its config.codetestFramework
My release pipeline relies on GitHub Actions. I often need to adjust version codes, enable ProGuard rules, or add Firebase performance monitoring. Instead of hunting through
build.gradle.kts“Add a Gradle task that increments the versionCode by 1, creates a git tag
, and pushes it, only when the branch iscodev<versionName>.<newVersionCode>.”codemain
The response:
// versionIncrement.gradle.kts
plugins {
id("com.github.hierynomus.license") version "0.17.0"
}
val incrementVersion by tasks.registering(DefaultTask::class) {
group = "verification"
description = "Bump versionCode and create a git tag"
doLast {
val versionCodeProperty = project.extensions.getByName("android") as com.android.build.gradle.internal.dsl.BaseExtension
val current = versionCodeProperty.versionCode.get()
val next = current + 1
versionCodeProperty.versionCode.set(next)
println("VersionCode bumped from $current to $next")
val versionName = versionCodeProperty.versionName.get()
val tag = "v$versionName.$next"
val git = "git"
listOf(git, "tag", tag).execute().waitFor()
listOf(git, "push", "origin", tag).execute().waitFor()
println("Tag $tag pushed")
}
}
// Attach to the assembleRelease lifecycle
tasks.named("assembleRelease") {
dependsOn(incrementVersion)
}I drop this file into
gradle/build.gradle.kts./gradlew incrementVersion[!WARNING]
Always review generated Gradle scripts before applying them. A misplacedcan cause cyclic dependencies that break the build.codeapply from:
Before MCP, I’d spend 5‑7 minutes searching for the correct DSL syntax, testing locally, and pushing a trial commit. Now I generate, review (≈1 minute), and apply—saving 4‑5 minutes per adjustment.
| Activity | Avg. Time Before MCP | Avg. Time After MCP | Time Saved per Instance |
|---|---|---|---|
| Compose UI screen creation | 4 min | 0.5 min | 3.5 min |
| Unit test file generation | 8 min | 1 min | 7 min |
| Gradle/CI tweak | 6 min | 1 min | 5 min |
| Debugging a crash log | 3 min | 0.75 min | 2.25 min |
| Daily total (≈15 instances) | ≈90 min | ≈12 min | ≈78 min |
Over a five‑day workweek, that’s 6.5 hours reclaimed—time I now invest in feature planning, user feedback, or simply resting.
By treating the MCP server as an ever‑present pair programmer, I’ve transformed my solo Android workflow from a series of context‑switching chores into a fluid, focused development stream. Give it a try; the time you recover will compound across every app you ship.
Sudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadPrivate dream journal — structured entry capture, pattern tagging, and optional Claude-powered insight generation. All data stays on-device by default.
ReadWorkout tracker — exercise logging with set/rep/weight history, goal progression, and local Room DB persistence. No account, no cloud sync required.
Read