◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 6 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 6 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/Advanced
⬡ Advanced/2026-05-06Advanced

Production-Grade Gemma 4 + Ollama + Android Studio — Task Routing, Fine-Tuning, Team Deployment, and CI Integration

A deep-dive into running Gemma 4 locally for Android development at production scale. Covers model-routing proxies, LoRA fine-tuning for project-specific patterns, Docker Compose team setup, and GitHub Actions AI code review integration.

Android StudioGemma 412Ollama8local LLM6fine-tuningAndroid developmentCI/CD

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 Modelfile

Team 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:11435

GitHub 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.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Advanced2026-10-14
Fine on Desktop, Blank on Safari: Four Things to Gather Before You Ask an AI to Diagnose It
After a shuffle, my lazy-loaded images never loaded again, but only in Safari on iPhone. Here is what to collect by hand before asking Gemini or any other model to diagnose a bug that does not reproduce on desktop, plus a request template you can paste.
⬡ Advanced2026-10-10
Keeping Money and Health Questions Out of Your History: Gemini Temporary Chat and the Settings to Check
Before you ask Gemini about household spending or a health worry, here is the order I use to check Temporary Chat and the Activity setting, so you can decide what gets kept and what doesn't.
⬡ Advanced2026-09-24
I Stopped Asking Gemini 'Is This Right?' and Started Hiding My Answer Instead
Ask Gemini to confirm a label you already chose and it tends to agree. Here is why I compared three ways of asking, chose blind re-classification, how my answer still leaked in, and the routing code that sends only disagreements to a human.