RORK LABJP
PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updatesPLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates
Articles/App Dev
App Dev/2026-04-11Intermediate

Gemma 4 for App Development: On-Device AI on the Cutting Edge

Learn how to integrate Google's Gemma 4 E2B and E4B models into iOS and Android apps for on-device AI. Covers privacy, offline capability, low latency, and practical code for real app use cases.

Gemma 42app development39on-device AI5iOS110Android47Rork547mobile AI

Google's Gemma 4, released on April 2, 2026, opens up new possibilities for mobile app developers. The E2B and E4B edge models are specifically designed for device-side deployment — small enough to run on smartphones, yet capable of handling text, images, and audio natively.

Why On-Device AI?

Before getting into implementation, it's worth being clear about why running inference locally is worth the effort.

Privacy by design. When inference happens on the device, user data never leaves it. This is significant for apps handling medical information, personal messages, financial data, or any content subject to privacy regulations like GDPR or HIPAA.

Offline capability. On-device models work without network connectivity. This matters more than it might seem — consider users in aircraft, subways, rural areas, or any situation where connectivity is unreliable.

Latency. Removing the network round-trip means responses feel immediate. For use cases like real-time camera analysis or voice interaction, this is the difference between a smooth experience and a frustrating one.

Cost at scale. Per-token API costs add up quickly at scale. On-device inference shifts the cost to hardware — a one-time consideration per user rather than a recurring operational expense.

Gemma 4 E2B and E4B Specs

Both edge models share the same capabilities:

  • Context window: 128,000 tokens
  • Input modalities: text, images, video, and audio (native)
  • Languages: 140+ natively
  • License: Apache 2.0

E2B is optimized for the smallest possible footprint, suitable for older devices and battery-sensitive deployments. E4B provides higher accuracy for edge use cases where quality matters more than raw efficiency.

iOS Integration via MediaPipe

Google's MediaPipe framework provides the most straightforward path to Gemma 4 on iOS.

import MediaPipeTasksGenAI
import UIKit
 
class GemmaInferenceManager {
    private var llmInference: LlmInference?
    
    func initialize() throws {
        // The model file must be bundled with the app
        guard let modelPath = Bundle.main.path(
            forResource: "gemma-4-e2b-it",
            ofType: "task"
        ) else {
            throw GemmaError.modelNotFound
        }
        
        let options = LlmInference.Options(modelPath: modelPath)
        options.maxTokens = 1024
        options.temperature = 0.7
        options.topK = 40
        
        llmInference = try LlmInference(options: options)
    }
    
    // Text generation
    func generateText(_ prompt: String) throws -> String {
        guard let inference = llmInference else {
            throw GemmaError.notInitialized
        }
        return try inference.generateResponse(inputText: prompt)
    }
    
    // Image + text (multimodal)
    func analyzeImage(_ image: UIImage, question: String) throws -> String {
        guard let inference = llmInference else {
            throw GemmaError.notInitialized
        }
        
        let mpImage = try MPImage(uiImage: image)
        return try inference.generateResponse(
            inputText: question,
            inputImages: [mpImage]
        )
    }
}
 
enum GemmaError: Error {
    case modelNotFound
    case notInitialized
}
 
// Usage example
class ProductScanViewController: UIViewController {
    let gemma = GemmaInferenceManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        try? gemma.initialize()
    }
    
    func processScannedImage(_ image: UIImage) {
        DispatchQueue.global(qos: .userInitiated).async {
            let result = try? self.gemma.analyzeImage(
                image,
                question: "Extract the product name, price, and expiry date from this image. Return as JSON."
            )
            DispatchQueue.main.async {
                // Update UI with result
                print(result ?? "No result")
            }
        }
    }
}

Android Integration

For Android, MediaPipe's Kotlin API provides a clean integration path:

import com.google.mediapipe.tasks.genai.llminference.LlmInference
import com.google.mediapipe.tasks.genai.llminference.LlmInferenceOptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
 
class GemmaManager(private val context: Context) {
    
    private var llmInference: LlmInference? = null
    
    suspend fun initialize() = withContext(Dispatchers.IO) {
        // Model file should be downloaded/bundled to internal storage
        val modelPath = "${context.filesDir.path}/gemma-4-e4b-it.task"
        
        val options = LlmInferenceOptions.builder()
            .setModelPath(modelPath)
            .setMaxTokens(1024)
            .setTemperature(0.7f)
            .setTopK(40)
            .build()
        
        llmInference = LlmInference.createFromOptions(context, options)
    }
    
    // Text generation
    suspend fun generate(prompt: String): String = withContext(Dispatchers.IO) {
        llmInference?.generateResponse(prompt) ?: throw IllegalStateException("Not initialized")
    }
    
    // Multimodal: image + text
    suspend fun analyzeImage(bitmap: Bitmap, question: String): String = withContext(Dispatchers.IO) {
        val mpImage = BitmapImageBuilder(bitmap).build()
        llmInference?.generateResponse(question, listOf(mpImage))
            ?: throw IllegalStateException("Not initialized")
    }
    
    fun release() = llmInference?.close()
}
 
// ViewModel integration
class ScanViewModel(application: Application) : AndroidViewModel(application) {
    private val gemmaManager = GemmaManager(application)
    val analysisResult = MutableLiveData<String>()
    
    init {
        viewModelScope.launch {
            gemmaManager.initialize()
        }
    }
    
    fun analyzeReceipt(bitmap: Bitmap) {
        viewModelScope.launch {
            val result = gemmaManager.analyzeImage(
                bitmap,
                "Extract all line items and total amount from this receipt. Return as JSON."
            )
            analysisResult.postValue(result)
        }
    }
    
    override fun onCleared() {
        super.onCleared()
        gemmaManager.release()
    }
}

Rork + Gemma 4 via Local API

If you're building with Rork, you can connect to a local Gemma 4 instance running via LM Studio during development, then switch to Vertex AI for production:

// Rork app: Gemma 4 integration via OpenAI-compatible API
const GEMMA_API_URL = __DEV__
  ? "http://localhost:1234/v1/chat/completions"  // LM Studio for development
  : "https://YOUR_VERTEX_AI_ENDPOINT/v1/chat/completions"; // Production
 
const callGemma4 = async (prompt, imageBase64 = null) => {
  const messageContent = imageBase64
    ? [
        { type: "image_url", image_url: { url: `data:image/jpeg;base64,${imageBase64}` } },
        { type: "text", text: prompt }
      ]
    : prompt;
 
  const response = await fetch(GEMMA_API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...(imageBase64 ? {} : {}) // Add auth header for production
    },
    body: JSON.stringify({
      model: "gemma-4-e4b",
      messages: [{ role: "user", content: messageContent }],
      temperature: 0.7,
      max_tokens: 512
    })
  });
 
  if (!response.ok) throw new Error(`API error: ${response.status}`);
  const data = await response.json();
  return data.choices[0].message.content;
};
 
// Practical usage: auto-tag photos
const tagPhoto = async (photo) => {
  const base64 = await FileSystem.readAsStringAsync(photo.uri, {
    encoding: FileSystem.EncodingType.Base64
  });
  
  const tags = await callGemma4(
    "Identify and list the key subjects, objects, and settings in this photo. Return as a JSON array of strings.",
    base64
  );
  
  return JSON.parse(tags);
};

Practical Use Cases

The combination of on-device inference, multimodal input, and Apache 2.0 licensing enables some compelling app features:

Real-time translation. Point the camera at a sign or menu, and Gemma 4 handles OCR and translation simultaneously. Works offline — critical for travel apps where roaming data may be unavailable.

Voice memo organization. Use E2B/E4B's native audio input to transcribe and categorize voice notes automatically. The on-device processing means no audio ever hits a server.

Receipt and document processing. Photograph a receipt and automatically extract line items, totals, and merchant information into your expense tracking app. No cloud dependency, no privacy concerns.

Healthcare screening support. Apps that analyze photos for preliminary assessments (skin conditions, food portions, exercise form) can use on-device processing to keep sensitive imagery local. (Always with appropriate medical disclaimers.)

Smart camera features. Real-time object identification, text recognition, or scene description overlaid on the camera view — responsive enough to feel native because inference is local.

Performance Considerations

A few things to keep in mind for production deployments:

Model loading takes time. Initialize the model when the app launches or during a natural pause, not on first use. Cache the initialized model in memory across your app session.

Battery impact is real. LLM inference on mobile is compute-intensive. Consider implementing inference budgets — limiting the length and frequency of model calls for less critical features.

Model file size matters. E2B at ~1.5GB and E4B at ~2.5GB are significant downloads. Use on-demand downloading with progress indication rather than bundling in the app binary.

Wrapping up

Gemma 4's E2B and E4B models make on-device AI a practical option for mainstream app development — not a research project. The combination of multimodal input, reasonable model sizes, and an open license removes several barriers that previously made on-device LLMs impractical for most apps.

Whether you're building with native Swift, Kotlin, or Rork, the integration paths are now clear. Start with a focused use case where privacy or offline capability genuinely matters to your users, prove it works, and build from there.

Share

Thank You for Reading

Rork 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 $15 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

App Dev2026-08-18
The three places I had to fix before a Rork project actually targeted API level 36
My app.json said targetSdkVersion 36. The value my build actually read was 35. Here is the script that reports the effective value, and how I split my apps between raising, leaving alone, and requesting an extension.
App Dev2026-06-29
Clipboard UX in Expo apps — copy and paste without flooding users with iOS's paste banner
When you wire up copy and paste with expo-clipboard, iOS's paste permission banner can fire constantly and quietly erode trust. Here's exactly when the banner appears, and how hasStringAsync lets you gate a Paste button without ever reading the contents.
App Dev2026-06-23
Why Wallpapers Look Dull on Device: Taming Display P3 in the Delivery Pipeline
The same wallpaper looked dull once set on device. The culprit was a mix-up between wide-gamut Display P3 and sRGB. Beyond embedding profiles, here is how to tell whether the pixels are truly wide-gamut, a pre-delivery gate script, and the Android wide-color story, across six wallpaper apps.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →