camera-samples Documentation

Multiple samples showing the best practices in camera APIs on Android.

5,468 stars Kotlin Kotlin 1 file ยท ~8,090 tokens #kotlin#sample#samples
RAW Doc

File: README.md

Android Camera Samples Catalog

A single, stand-alone Android app that showcases the Camera2 and CameraX APIs through a
catalog of small, self-contained, Compose-first samples. Each sample focuses on one feature and
reuses a shared camera-scaffolding layer, so adding a new sample means writing the feature โ€” not the
boilerplate.

Screenshots

The home catalog in the "Console" theme โ€” a violet accent, monospace metadata, a bordered featured
card, and a dense 2-column grid. The filter pills narrow the catalog by category, and samples are
grouped CameraX-first, Camera2 below.

How to run

  1. Clone the repository.
  2. Open it in a recent Android Studio.
  3. Run the app configuration on a device or emulator. Camera-dependent samples (extensions,
    slow-motion, ML) behave best on a physical device; emulators with a virtual camera cover the
    basics.

No Firebase / google-services.json is required.

Samples

Filter the catalog by category (Images / Video / ML / Graphics / Extensions / Controls) on the
home screen. Within each view, CameraX samples are listed first and Camera2 below.

Sample API What it shows
Take a Photo CameraX / Camera2 Preview, tap-to-focus, capture a still
Take a Video CameraX (Recorder) / Camera2 (MediaRecorder) Record video to DCIM/Camera
Pause/Resume CameraX (Recorder) Pause and resume a recording mid-capture
Slow Motion Camera2 High-speed (constrained) recording
HDR Video CameraX / Camera2 Record & play back 10-bit HDR video (DynamicRange / DynamicRangeProfiles)
Video Stabilization CameraX Steadier footage via preview + video stabilization
Flip While Recording CameraX Switch front/back cameras without interrupting the recording
QR Scanner CameraX / Camera2 + ML Kit Real-time barcode/QR detection with overlay
Image Labeling CameraX + ML Kit Real-time on-device labels via ImageAnalysis
Luminosity CameraX Scene-brightness meter from the ImageAnalysis Y plane
Green Screen CameraX + ML Kit Concurrent cameras: segment the front-camera subject over the live back camera
Extensions CameraX / Camera2 Night / Bokeh / HDR / Face Retouch / Auto
Ultra HDR CameraX Gain-map Ultra HDR (JPEG_R) capture + a viewer to compare SDR / gain map / Ultra HDR
Effects CameraX Live color filters (grayscale, sepia, invert, โ€ฆ) via ImageAnalysis + ColorMatrix
Viewfinder Effects Camera2 Real-time frame processing on the live preview
Concurrent Camera CameraX Front + back preview at once (picture-in-picture)
RAW / DNG Camera2 RAW_SENSOR โ†’ DNG via DngCreator, then an in-app viewer/editor (re-expose, re-white-balance)
Zoom & Torch CameraX / Camera2 Zoom ratio slider + torch toggle
Exposure CameraX Exposure-compensation slider
Low-Light Boost CameraX Brighten dark scenes with CameraControl low-light boost
Feature Combination CameraX Query & apply CameraX feature-group combos (HDR, 60fps, stabilization, Ultra HDR)
Manual Controls Camera2 Manual ISO / shutter / focus distance

Samples that depend on optional hardware (extensions, high-speed recording, manual sensor) detect
support at runtime and show a friendly "not supported on this device" state instead of crashing.

Architecture

Every sample is a Gradle library module under samples/{api}-{feature}/
(package com.android.{api}.{feature}) exposing a single {Api}{Feature}Screen() composable. It
follows the layered, unidirectional pattern in android_architecture.md:

  • {Feature}UiState โ€” a sealed interface of states (Initial, feature states, Error).
  • {Feature}ViewModel โ€” a @HiltViewModel exposing a single StateFlow; no
    Android/lifecycle references.
  • {Feature}Controller โ€” a @Stable state-holder that owns the camera SDK lifecycle, created
    with a remember{Feature}Controller(...) composable.
  • {Feature}Screen โ€” collects state with collectAsStateWithLifecycle, wraps everything in the
    shared scaffold, and renders with a when(state).

Shared modules

The scaffolding that used to be copy-pasted into every sample lives in three shared modules, so a new
sample focuses only on its feature:

  • :core-theme โ€” the design system: the Material 3 color scheme with the violet "Console" accent,
    Space Grotesk / Space Mono typography (via Google Fonts), and the AISampleCatalogTheme wrapper.
  • :core-camera โ€” camera plumbing: BaseCamera2Controller (background thread, open/close,
    viewfinder transform, tap-to-focus, session creation), Camera2Preview / CameraXPreview,
    ImageUtils (Image/ImageProxy โ†’ Bitmap), MediaStoreSaver, rememberDisplayRotation(),
    and CameraPermissions. It re-exports the common CameraX/Camera2 libraries.
  • :core-ui โ€” API-agnostic Compose chrome: CameraSampleScaffold (permission flow + surface),
    the viewfinder HUD (FocusIndicator reticle, RuleOfThirdsGrid, ViewfinderTitleChip,
    TorchChip), ShutterButton / RecordButton / CameraControlsBar / ScrimIconButton,
    ValueSlider / ZoomControls, CapturedImagePreview / CapturedVideoPreview / VideoPlayer,
    the SettingsOverlay menu, and the loading / error / unsupported state views. It re-exports
    :core-camera and :core-theme.

A sample is registered in one place โ€” a SampleCatalogItem in
SampleCatalog.kt โ€” and the
app's NavHost is derived from that list automatically.

Adding a new sample

Use the generator, which scaffolds a working (preview-only) compose-first module and wires it into
the build and catalog:

bash
./gradlew createSample \
  -PsampleName="camera2-flash" \
  -PscreenName="Camera2FlashScreen" \
  -Ptitle="Camera2 โ€ข Flash" \
  -Pdesc="Toggle the flash with Camera2" \
  -Ptype="camera2"          # camera2 (default) or camerax

./gradlew spotlessApply      # format the generated files

Then implement the feature in the generated Controller / Screen and re-sync Gradle.

Code style

The project is formatted with Spotless (ktlint + Apache
license headers). Run ./gradlew spotlessApply before committing.


File: AGENTS.md

AGENTS.md โ€” Android Camera Samples

Guidance for AI coding agents (and humans) working in this repository. Read this before making
changes.
It describes the rules, architecture, design system, and shared components.

What this is

A single, Compose-first Android app โ€” the Camera Samples Catalog โ€” that showcases the
Camera2 and CameraX APIs through 28 small, self-contained samples. A home catalog routes to
each sample; every sample is its own Gradle library module and reuses a shared camera / UI / theme
layer, so adding a sample means writing the feature, not the boilerplate.

TL;DR โ€” rules you must not violate

  1. Viewfinder. CameraX previews use CameraXViewfinder; Camera2 previews use the Compose
    Viewfinder. Never use PreviewView, ViewfinderView, SurfaceView, or TextureView.
    In practice always render through the shared CameraXPreview / Camera2Preview composables
    (:core-camera) โ€” there are no Android Views in sample/UI code.
  2. One feature per sample. Each sample demonstrates exactly one capability. Don't bundle features
    unless a feature genuinely requires another (e.g. Green Screen = concurrent camera + ML Kit
    segmentation, Feature Combination = several CameraX features by design).
  3. No loose strings. Every user-facing string lives in a module's res/values/strings.xml and is
    read via stringResource(...) / context.getString(...). No string literals in Text(...),
    contentDescription, button labels, etc.
  4. Reuse the shared layers (:core-camera, :core-ui, :core-theme). Don't reinvent shutter
    buttons, control bars, scaffolds, previews, sliders, overlays, theme tokens, etc.
  5. Layered architecture (see below): UiState โ†’ ViewModel โ†’ Controller โ†’ Screen.
    ViewModels never reference Context, Activity, lifecycle, or Resources.
  6. Guard optional hardware โ†’ render UnsupportedView instead of crashing.
  7. Apache license header on every file; run ./gradlew spotlessApply (ktlint + headers)
    before finishing.

Repository layout / module graph

text
:app                      home catalog + Navigation-Compose NavHost + Hilt application
:core-theme               design system (colors, typography, AISampleCatalogTheme)        โ€” leaf
:core-camera              Camera2/CameraX plumbing (controllers, previews, utils)         โ€” re-exports camera libs
:core-ui                  shared Compose chrome (scaffold, controls, overlays, review)    โ€” api(:core-camera, :core-theme)
:samples:{api}-{feature}  one library module per sample                                   โ€” depends on :core-camera + :core-ui

Dependency direction is acyclic: app โ†’ core-theme + samples; samples โ†’ core-camera + core-ui;
core-ui โ†’ api(core-camera, core-theme); core-camera and core-theme are leaves.

Naming. Module samples/{api}-{feature} โ†’ package com.android.{api}.{feature} โ†’ composables
{Api}{Feature}Screen / {Api}{Feature}ViewModel / {Api}{Feature}Controller /
{Api}{Feature}UiState, where {api} โˆˆ {camerax, camera2}.

Build, run, format

  • JDK 17 toolchain (jvmToolchain(17)); compileSdk / targetSdk 37 (Android 17), minSdk 23.
  • Build everything: ./gradlew assembleDebug ยท Install: ./gradlew :app:installDebug.
  • Format (required, CI-gated): ./gradlew spotlessApply (auto-fix) / ./gradlew spotlessCheck.
    Spotless = ktlint + the Apache header from spotless/copyright.kt. .editorconfig sets
    ktlint_function_naming_ignore_when_annotated_with = Composable so PascalCase @Composable names
    pass.
  • Key versions (gradle/libs.versions.toml): AGP 9.2.1, Kotlin 2.4.0, Gradle 9.6, Compose BOM
    2026.06.00, Material 3 1.5.0-alpha22, androidx.camera 1.6.1 (camera-compose,
    camera-viewfinder-compose), Media3 1.10.1, Hilt 2.59.2, Navigation-Compose 2.9.8,
    kotlinx-serialization 1.11.0, ML Kit (barcode 17.3, image-labeling 17.0.9, selfie-segmentation
    16.0.0-beta6), KSP 2.3.5, Spotless 8.7.0.

Architecture & programming paradigm

Layered, unidirectional (UDF), Compose-first โ€” see android_architecture.md.
Each sample is four pieces:

  • {Feature}UiState โ€” a sealed interface of states: Initial, one or more feature states
    (e.g. Previewing, Capturing, Recording, Captured), and Error(message). Add Unsupported
    when hardware support is conditional.
  • {Feature}ViewModel โ€” @HiltViewModel, @Inject constructor. Exposes exactly one
    val uiState: StateFlow<{Feature}UiState> (a private MutableStateFlow surfaced immutably).
    No Context/Activity/lifecycle/Resources; no events pushed to the UI (process the
    event, update state). Business logic lives here.
  • {Feature}Controller โ€” a @Stable state-holder that owns the camera SDK lifecycle
    (open/close, sessions, capture/record). Created from the Screen via a remember{Feature}Controller(...)
    composable. Camera2 controllers extend BaseCamera2Controller; CameraX controllers wrap
    ProcessCameraProvider. The controller is the only place that holds Context.
  • {Feature}Screen โ€” the screen-level composable. Collects with collectAsStateWithLifecycle(),
    wraps content in CameraSampleScaffold(...), and branches with when (state).

Conventions

  • Lifecycle-aware collection (collectAsStateWithLifecycle); never override Activity lifecycle methods
    โ€” use LifecycleEventObserver + DisposableEffect.
  • Every reusable composable (not the top-level Screen) takes modifier: Modifier = Modifier as its
    first optional parameter.
  • Single-activity app + Navigation-Compose; the NavHost is derived from sampleCatalog.
  • Single controller call-site for long-lived state. When several states all show the camera
    (Previewing / Recording / Captured), create the controller from one call site (e.g. a
    shared else -> branch), not separately per when branch โ€” otherwise a state change disposes and
    recreates the controller and kills an in-progress recording. Overlay review screens on top of
    the live content instead of swapping the whole branch.
  • DI: Hilt โ€” @HiltAndroidApp Application, @AndroidEntryPoint MainActivity, ViewModels via
    hiltViewModel(...).

The viewfinder rule (expanded)

  • CameraX โ†’ render the SurfaceRequest through CameraXPreview (:core-camera), which uses
    androidx.camera.compose.CameraXViewfinder.
  • Camera2 โ†’ render through Camera2Preview (:core-camera), which uses
    androidx.camera.viewfinder.compose.Viewfinder driven by a BaseCamera2Controller.
  • Never instantiate PreviewView, ViewfinderView, SurfaceView, or TextureView. Even the
    captured-video player is Compose (Media3 PlayerSurface), not a VideoView.

Strings & resources

  • Each sample module owns its res/values/strings.xml. No hardcoded user-facing text anywhere.
  • Catalog strings live in app/src/main/res/values/strings.xml with keys
    {api}_{feature}_list_title and {api}_{feature}_list_description (title format e.g.
    "CameraX โ€ข Take a Photo"). In-screen strings use the sample module's own keys.

UI / design system โ€” "Console"

A dark, pro/technical aesthetic (:core-theme + :core-ui).

  • Accent violet #B59CFF (primary) on #190F33 (onPrimary); dark surfaces; hairline borders.
  • Type: displayFontFamily / bodyFontFamily = Space Grotesk; monoFontFamily = Space Mono for
    all technical/metadata text (API labels, filter chips, counts, HUD readouts).
  • Home catalog (:app catalog/ui): mono header + live count, the SampleCategory filter pills
    (All ยท Images ยท Video ยท ML ยท Graphics ยท Extensions ยท Controls), a bordered featured hero
    (CatalogWideCard), and a 2-column grid of compact tiles (CatalogRowCard) grouped under CAMERAX
    / CAMERA2 section labels. The tile API label is accent for CameraX, muted for Camera2; a category
    dot uses the first tag's color.
  • Viewfinder chrome (:core-ui): scrim top-bar buttons (ScrimIconButton), centered title chip
    (ViewfinderTitleChip), RuleOfThirdsGrid, the accent square FocusIndicator reticle, a bottom
    CameraControlsBar with ShutterButton / RecordButton, TorchChip / TorchGlow, and
    ZoomControls.
  • In-app settings use SettingsOverlay (+ SettingsHeader / SettingsRow / SettingsDropdown
    accent chips / settingsSwitchColors) โ€” never raw Material dropdowns.
  • Captured-media review via CapturedImagePreview / CapturedVideoPreview.

Component reference (reuse these โ€” don't duplicate)

`:core-theme` (`com.android.camera.coretheme`)

  • AISampleCatalogTheme(darkTheme, contrast, content) โ€” app theme wrapper (Material 3 +
    AppTypography). Theme.kt
  • Color tokens (violet primary/onPrimary, dark surfaces, โ€ฆ) + extendedColorScheme
    (media3, mLKit, imagen, geminiProFlash, โ€ฆ consumed by SampleTags). Color.kt, Theme.kt
  • displayFontFamily, bodyFontFamily (Space Grotesk), monoFontFamily (Space Mono),
    AppTypography. Type.kt

`:core-camera` (`com.android.camera.core.*`)

  • BaseCamera2Controller โ€” @Stable abstract base: background-thread open/close, session creation
    (createCaptureSession), display-rotation transform, tap-to-focus (focus),
    surfaceRequest/transformationInfo state, release(); subclasses override onCameraOpened /
    configureOutput / etc. camera2/BaseCamera2Controller.kt
  • Camera2Preview(controller, modifier, onFocusTap) โ€” Compose Viewfinder host. camera2/Camera2Preview.kt
  • CameraXPreview(surfaceRequest, modifier, onTapToFocus, onFocusTap) โ€” CameraXViewfinder host
    with coordinate transform. camerax/CameraXPreview.kt
  • ImageProxy.toBitmap(mirror) / Image.toBitmap(sensorOrientation, mirror) โ€” JPEG โ†’ oriented
    Bitmap. image/ImageUtils.kt
  • MediaStoreSaver โ€” saveBitmap(...) to DCIM/Camera, newVideoFile(), scanFile(...). media/MediaStoreSaver.kt
  • CameraPermissions โ€” PHOTO, VIDEO permission lists. permissions/CameraPermissions.kt
  • rememberDisplayRotation(): Int โ€” current display rotation; recomposes on config change. display/DisplayRotation.kt

`:core-ui` (`com.android.camera.coreui.*`)

  • scaffold/ โ€” CameraSampleScaffold(permissions, api, โ€ฆ, content) (permission gate + black surface
    • API badge); CameraApi enum (CAMERA2, CAMERAX).
  • controls/ โ€” ShutterButton, RecordButton, CameraSwitchButton,
    ScrimIconButton(onClick, imageVector, โ€ฆ, size, iconSize), CameraControlsBar(startSlot, center, endSlot),
    ValueSlider(label, value, โ€ฆ, accentColor), ZoomControls(zoomRatio, valueRange, onValueChange, stops).
  • overlay/ โ€” FocusIndicator(tapOffset), RuleOfThirdsGrid(), ViewfinderTitleChip(text),
    TorchChip(on, onToggle), TorchGlow(visible), SettingsOverlay(visible, onDismiss, content),
    SettingsHeader(text), SettingsRow(label, trailing),
    SettingsDropdown(label, options, selected, onSelected, optionLabel), settingsSwitchColors().
  • preview/ โ€” CapturedImagePreview(bitmap, onRetake, onDone), CapturedVideoPreview(uri, onDismiss).
  • widget/ โ€” VideoPlayer(player) (Compose-first Media3 PlayerSurface; tap to play/pause),
    HdrWindowColorMode(enabled) (toggle host-window HDR color mode, API 26+).
  • state/ โ€” LoadingView(), ErrorView(errorMessage, onRetry), UnsupportedView(message).

Catalog model (`app/.../catalog/domain/SampleCatalog.kt`)

  • sampleCatalog: List<SampleCatalogItem> โ€” the single registry; the NavHost and the home grid
    both derive from it.
  • SampleCatalogItem(title, description, route, sampleEntryScreen, type, category, tags, isFeatured, keyArt).
  • SampleType = { CAMERAX, CAMERA2 }; SampleCategory =
    { IMAGES, VIDEO, ML, GRAPHICS, EXTENSIONS, CONTROLS } (drives the home filter); SampleTags =
    { MEDIA3, ML_KIT, VIDEO, EXTENSIONS, ANALYSIS } (drives the tile's category dot color).

Adding a new sample

  1. Generate:
    bash
    ./gradlew createSample \
      -PsampleName="camera2-flash" \
      -PscreenName="Camera2FlashScreen" \
      -Ptitle="Camera2 โ€ข Flash" \
      -Pdesc="Toggle the flash with Camera2" \
      -Ptype="camera2"          # camera2 (default) | camerax
    This scaffolds samples/camera2-flash/ (UiState / ViewModel / Controller / Screen + build.gradle.kts
    • strings.xml) and wires settings.gradle.kts, app/build.gradle.kts, the catalog
      (SampleCatalog.kt), and the catalog strings.
  2. ./gradlew spotlessApply.
  3. Implement the feature in the generated Controller + Screen; add in-screen strings to the
    module's strings.xml; set the new SampleCatalogItem's category (and tags / isFeatured if
    relevant).
  4. Honor every TL;DR rule โ€” reuse :core-ui / :core-camera, render via CameraXPreview /
    Camera2Preview, no loose strings, one feature.
  5. Verify: ./gradlew assembleDebug spotlessCheck.

File: android_architecture.md

Recommendations for Android Architecture

This document provides best practices and recommendations for Android architecture to improve app quality, robustness, scalability, maintainability, and testability. These are recommendations, not strict requirements, and should be adapted as needed. Recommendations are categorized by priority:

  • Strongly recommended: Implement unless there's a fundamental clash with your approach.
  • Recommended: Likely to improve your app.
  • Optional: Can improve your app in certain situations.

Familiarity with general Android architecture guidance is assumed.

Layered Architecture

The recommended layered architecture promotes separation of concerns, driving UI from data models, adhering to the single source of truth principle, and following unidirectional data flow.

  • Strongly recommended: Use a clearly defined data layer. This layer exposes application data and contains most business logic. Create repositories even with a single data source. In small apps, place data layer types in a data package or module.
  • Strongly recommended: Use a clearly defined UI layer. This layer displays data and handles user interaction. In small apps, place UI layer types in a ui package or module. More UI layer best practices are detailed later.
  • Strongly recommended: The data layer should expose data via a repository. UI components (composables, activities, ViewModels) should not interact directly with data sources (databases, DataStore, network, etc.).
  • Strongly recommended: Use coroutines and flows for inter-layer communication. More coroutines best practices are available elsewhere.
  • Recommended (in big apps): Use a domain layer and use cases for reusable business logic across multiple ViewModels or to simplify ViewModel logic.

UI Layer

The UI layer displays data and handles user interaction.

  • Strongly recommended: Follow Unidirectional Data Flow (UDF). ViewModels expose UI state using the observer pattern and receive actions via method calls.
  • Strongly recommended: Use AAC ViewModels for business logic and fetching data for UI state. More ViewModel best practices are detailed later. Benefits of ViewModels are discussed elsewhere.
  • Strongly recommended: Use lifecycle-aware UI state collection. Use repeatOnLifecycle in the View system and collectAsStateWithLifecycle in Jetpack Compose. In your composable, you can use a when expression to handle the state as follows:
kotlin
val uiState = viewModel.uiState.collectAsStateWithLifecycle()

when (uiState) {
    ScreenState.Initial -> {
      // Show initial state
    }

    ScreenState.Generating -> {
      // Show generating state
    }

    is ScreenState.Success -> {
      // Show success state
    }

    is ScreenState.Error -> {
      // Show error state
    }
}
  • Strongly recommended: Do not send events from the ViewModel to the UI. Process events in the ViewModel and update the state accordingly. More on UI events is available elsewhere.
  • Strongly recommended: Every composable function (except top level screen composable) should take a Modifier as a parameter with a default value. It should be positionned as the first optional parameter.
  • Recommended: Use a single-activity application. Use Navigation Fragments or Navigation Compose for navigation and deep linking.
  • Recommended: Use Jetpack Compose for new apps on phones, tablets, foldables, and Wear OS.

ViewModel

ViewModels provide UI state and data layer access.

  • Strongly recommended: ViewModels should be agnostic of the Android lifecycle. Avoid references to lifecycle-related types, Activities, Fragments, Context, or Resources. Evaluate if dependencies on Context belong in the ViewModel.
  • Strongly recommended: ViewModel functions should never take an activity as a parameter to avoid memory leaks.
  • Strongly recommended: Use coroutines and flows. ViewModels interact with data/domain layers using Kotlin flows for receiving data and suspend functions with viewModelScope for actions.
  • Strongly recommended: Use ViewModels at the screen level. Avoid using them in reusable UI components. Use them in screen-level composables, Activities/Fragments, and Navigation destinations/graphs.
  • Strongly recommended: Use plain state holder classes in reusable UI components. This allows external control over the state.
  • Strongly recommended: ViewModel should only expose one UI state defined as a sealed class. e.g:
kotlin
sealed class ScreenState {
   data object Initial : ScreenState()
   data object Generating : ScreenState()  // Use for generating content
   data class Success(val data: String) : ScreenState() // Use to display data
   data class Error(val message: String) : ScreenState() // Use for error state
}
  • Recommended: Do not use AndroidViewModel. Use the ViewModel class. Avoid using the Application class in ViewModels; move the dependency to the UI or data layer.
  • Recommended: Don't use LiveData, use state flow instead.
  • Recommended: Expose a UI state. Use a single uiState property (a StateFlow) for data exposure. Multiple properties can be used for unrelated data. Use stateIn with WhileSubscribed(5000) for data streams. For simpler cases, use a MutableStateFlow exposed as an immutable StateFlow. Consider using a data class or sealed class for the UiState.
  • Recommeded: Donโ€™t pass Context to your ViewModel. To avoid memory leaks only UI (Composables) should have a reference to context.
  • Recommeded: Donโ€™t use fetchData() in a ViewModel init {} block. If you do end up in a case where you need to do something on initial load, you use stateIn() instead.

Lifecycle

Best practices for working with the Android lifecycle:

  • Strongly recommended: Do not override lifecycle methods (e.g., onResume) in Activities or Fragments. Use LifecycleObserver instead. For lifecycle-dependent work, use repeatOnLifecycle.

Handle Dependencies

Best practices for dependency management:

  • Strongly recommended: Use dependency injection, primarily constructor injection.
  • Strongly recommended: Scope to a dependency container when sharing mutable data or for expensive, widely used types.
  • Recommended: Use Hilt for complex projects or manual dependency injection for simpler apps. Hilt is beneficial for multiple ViewModels, WorkManager usage, and advanced Navigation.

Testing

Best practices for testing:

  • Strongly recommended: Know what to test. Test ViewModels (including Flows), data layer entities (repositories and data sources), and UI navigation for regression testing.
  • Strongly recommended: Prefer fakes over mocks. See the Android documentation on test doubles.
  • Strongly recommended: Test StateFlows. Assert on the value property when possible. Create a collectJob when using WhileSubscribed.

Models

Best practices for models:

  • Recommended: Create a model per layer in complex apps. Consider creating separate models for different layers (e.g., remote data source, repositories, ViewModels) to simplify data structures and reduce dependencies.

Naming Conventions

Optional naming conventions:

  • Methods: Verb phrases (e.g., makePayment()).
  • Properties: Noun phrases (e.g., inProgressTopicSelection).
  • Streams: get{Model}Stream() (e.g., getAuthorStream(): Flow<Author>, getAuthorsStream(): Flow<List<Author>>).
  • Interface Implementations: Meaningful names. Prefix with Default if no better name is found (e.g., OfflineFirstNewsRepository, InMemoryNewsRepository, DefaultNewsRepository). Prefix fake implementations with Fake (e.g., FakeAuthorsRepository).

File: samples/camera2-takeaphoto/README.md

Gemini Multimodal Sample

This sample is part of the AI Sample Catalog. To build and run this sample, you should clone the entire repository.

Description

This sample demonstrates a multimodal (image and text) prompt, using the Gemini Flash model. Users can select an image and provide a text prompt, and the generative model will respond based on both inputs. This showcases how to build a simple, yet powerful, multimodal AI with the Gemini API.

How it works

The application uses the Firebase AI SDK (see How to run) for Android to interact with Gemini Flash. The core logic is in the GeminiDataSource.kt file. A generativeModel is initialized, and then a chat session is started from it. When a user provides an image and a text prompt, they are combined into a multimodal prompt and sent to the model, which then generates a text response.

Here is the key snippet of code that initializes the generative model:

kotlin
private val generativeModel by lazy {
    Firebase.ai(backend = GenerativeBackend.googleAI()).generativeModel(
        "gemini-2.5-flash",
        generationConfig = generationConfig {
            temperature = 0.9f
            topK = 32
            topP = 1f
            maxOutputTokens = 4096
        },
        safetySettings = listOf(
            SafetySetting(HarmCategory.HARASSMENT, HarmBlockThreshold.MEDIUM_AND_ABOVE),
            SafetySetting(HarmCategory.HATE_SPEECH, HarmBlockThreshold.MEDIUM_AND_ABOVE),
            SafetySetting(HarmCategory.SEXUALLY_EXPLICIT, HarmBlockThreshold.MEDIUM_AND_ABOVE),
            SafetySetting(HarmCategory.DANGEROUS_CONTENT, HarmBlockThreshold.MEDIUM_AND_ABOVE),
        ),
    )
}

Here is the key snippet of code that calls the generateText function:

kotlin
suspend fun generateText(bitmap: Bitmap, prompt: String): String {
    val multimodalPrompt = content {
        image(bitmap)
        text(prompt)
    }
    val result = generativeModel.generateContent(multimodalPrompt)
    return result.text ?: ""
}

Read more about the Gemini API in the Android Documentation.


File: .github/ISSUE_TEMPLATE/bug-report.md


name: Bug Report
about: Issue related to sample code found in this repo
title: ''
labels: ''


Please follow the following instructions before filing a bug:

  1. Github issues under this project are only for sample-related issues. If you have modified the sample code in any way, then this is probably not the appropriate place to report a bug.
  2. If you are trying to report a bug related to the CameraX library then you can do so at the Android Issue Tracker: https://issuetracker.google.com/components/618491.
  3. Include the specific sample where you found the issue, in addition to device model and specific Android build number -- which can be found under Settings > About phone.
  4. Include error logs from logcat, if there are any.
  5. If you can, also attach a bug report-- but please keep in mind that personal information can sometimes be found in those, so double check before attaching.

File: .github/workflows/android.yml

Licensed under the Apache License, Version 2.0 (the "License");

you may not use this file except in compliance with the License.

You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software

distributed under the License is distributed on an "AS IS" BASIS,

WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the License for the specific language governing permissions and

limitations under the License.

name: Android CI

on:
workflow_dispatch:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
name: Build & check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

text
- name: Set up JDK 17
    uses: actions/setup-java@v4
    with:
      distribution: 'zulu'
      java-version: '17'
      cache: gradle

  - name: Set up Android SDK
    uses: android-actions/setup-android@v3

  # The project compiles against API 37 (Android 17); install that platform.
  - name: Install compileSdk 37 platform
    run: yes | sdkmanager "platforms;android-37.0" > /dev/null

  - name: Check formatting (Spotless)
    run: ./gradlew spotlessCheck

  - name: Assemble debug
    run: ./gradlew assembleDebug

  - name: Upload debug APK
    uses: actions/upload-artifact@v4
    with:
      name: app-debug
      path: app/build/outputs/apk/debug/app-debug.apk