Getting Gemma 4 connected to Android Studio is straightforward. Getting it to work reliably across a team, fine-tuned to your project's patterns, and integrated into your CI pipeline — that requires more deliberate architecture.
Model Selection Strategy: Route by Task Type
The first design decision: don't commit to one model size. A 26B model produces better code review than a 4B model, but it's also much slower for inline completions. The practical answer is routing different tasks to different model sizes.
A lightweight FastAPI proxy handles this automatically:
# ollama_router.py
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import httpx
app = FastAPI()
MODEL_ROUTING = {
"completion": "gemma4:9b", # Speed priority
"review": "gemma4:26b", # Quality priority
"generate": "gemma4:26b", # Zero-to-code generation
"default": "gemma4:9b",
}
def detect_task_type(prompt: str) -> str:
prompt_lower = prompt.lower()
if any(w in prompt_lower for w in ["review", "bug", "error", "problem", "issue"]):
return "review"
if any(w in prompt_lower for w in ["generate", "create", "write", "implement"]):
return "generate"
return "default"
@app.post("/api/generate")
async def route_generation(request: Request):
body = await request.json()
task_type = detect_task_type(body.get("prompt", ""))
body["model"] = MODEL_ROUTING.get(task_type, MODEL_ROUTING["default"])
async with httpx.AsyncClient(timeout=120.0) as client:
async with client.stream(
"POST", "http://localhost:11434/api/generate", json=body
) as response:
return StreamingResponse(
response.aiter_bytes(), media_type="application/x-ndjson"
)Run the proxy on port 11435, then point Android Studio to http://localhost:11435 instead of the default Ollama port.
Fine-Tuning Gemma 4 on Your Codebase
Base Gemma 4 knows Android patterns in general. Fine-tuning on your own code teaches it your specific architecture — your naming conventions, your DI patterns, your error handling idioms.
Prepare Training Data
Extract instruction-completion pairs from your existing Kotlin files:
# prepare_training_data.py
import json
from pathlib import Path
def extract_pairs(repo_path: str) -> list[dict]:
pairs = []
for kotlin_file in Path(repo_path).rglob("*.kt"):
content = kotlin_file.read_text(encoding="utf-8")
pairs.append({
"prompt": f"Implement this Kotlin method signature:\n{extract_signature(content)}",
"completion": extract_implementation(content),
})
return pairs
def save_jsonl(output_path: str, pairs: list[dict]):
with open(output_path, "w") as f:
for pair in pairs:
f.write(json.dumps(pair) + "\n")
pairs = extract_pairs("/path/to/your/android/project")
save_jsonl("training_data.jsonl", pairs)LoRA Fine-Tuning
Full fine-tuning is prohibitively expensive for most teams. LoRA achieves strong results by training only a small adapter layer:
# fine_tune_gemma.py
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer
import torch
def fine_tune(base_model: str, data_path: str, output_dir: str):
tokenizer = AutoTokenizer.from_pretrained(base_model)
model = AutoModelForCausalLM.from_pretrained(
base_model,
torch_dtype=torch.bfloat16,
device_map="auto",
load_in_4bit=True
)
lora_config = LoraConfig(
r=16, lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05, bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
trainer = SFTTrainer(
model=model,
args=TrainingArguments(
output_dir=output_dir,
num_train_epochs=3,
per_device_train_batch_size=4,
learning_rate=2e-4,
fp16=True
),
train_dataset=load_dataset("json", data_files=data_path)["train"],
tokenizer=tokenizer,
max_seq_length=2048
)
trainer.train()
trainer.save_model(output_dir)Register the fine-tuned model in Ollama:
# Modelfile
FROM ./gemma4-android-finetuned
SYSTEM """
You are an expert Android/Kotlin assistant for this project.
- Use ViewModels for UI state management
- Always access data through the Repository layer
- Use Kotlin coroutines, not RxJava
- Inject dependencies with Hilt
"""
PARAMETER temperature 0.3
ollama create gemma4-android-project -f ModelfileTeam Deployment with Docker Compose
The goal: any team member runs two commands and has a fully configured local AI environment.
# docker-compose.yml
version: "3.8"
services:
ollama:
image: ollama/ollama:latest
ports:
- "11434:11434"
volumes:
- ollama_models:/root/.ollama
- ./models:/models
restart: unless-stopped
ollama-router:
build: ./ollama-router
ports:
- "11435:11435"
environment:
- OLLAMA_URL=http://ollama:11434
depends_on: [ollama]
restart: unless-stopped
model-initializer:
image: ollama/ollama:latest
volumes:
- ollama_models:/root/.ollama
- ./scripts:/scripts
entrypoint: ["/scripts/init-models.sh"]
depends_on: [ollama]
volumes:
ollama_models:# scripts/init-models.sh
#!/bin/bash
ollama pull gemma4:9b
ollama pull gemma4:26b
if [ -f /models/Modelfile.android ]; then
ollama create gemma4-android-project -f /models/Modelfile.android
fi
echo "Models ready"New team member setup:
git clone <repo>
docker compose up -d
# Set Android Studio endpoint to http://localhost:11435GitHub Actions AI Code Review
Running AI review automatically on pull requests catches architecture issues before human review:
# .github/workflows/ai-review.yml
name: AI Code Review
on:
pull_request:
paths: ['app/src/**/*.kt']
jobs:
ai-review:
runs-on: self-hosted # Runner with local Ollama
steps:
- uses: actions/checkout@v4
- name: Start Ollama
run: docker compose up -d ollama
- name: Wait for readiness
run: until curl -sf http://localhost:11434/api/version; do sleep 2; done
- name: Run AI review
run: |
for file in $(git diff --name-only origin/main -- '*.kt'); do
echo "## $file" >> review.md
python scripts/ai_review.py "$file" >> review.md
done
- name: Post PR comment
uses: actions/github-script@v7
with:
script: |
const review = require('fs').readFileSync('review.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## AI Code Review (Gemma 4)\n\n${review}`
});# scripts/ai_review.py
import sys, httpx
def review_file(path: str) -> str:
code = open(path).read()
response = httpx.post(
"http://localhost:11434/api/generate",
json={
"model": "gemma4:26b",
"prompt": f"Review this Kotlin file for Android best practices. "
f"Flag architecture issues, memory leak risks, and testability concerns. "
f"Maximum 3 suggestions:\n\n```kotlin\n{code}\n```",
"stream": False
},
timeout=120.0
)
return response.json()["response"]
print(review_file(sys.argv[1]))What Changes in Practice
The most surprising outcome was that AI suggestions started reflecting team conventions after fine-tuning. Generic cloud AI gives you general Android patterns. A model trained on your codebase suggests how your team would write the code.
The ongoing challenge is knowledge decay. Android and Jetpack Compose evolve quickly; a fine-tuned model's knowledge stops at training time. After significant API updates, plan a retraining cycle.
A hybrid approach works well in practice: local Gemma 4 for proprietary code and routine tasks, cloud Gemini for researching new APIs and framework changes. The architecture described here supports routing requests to either target, which makes that hybrid easy to manage.