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
- Clone the repository.
- Open it in a recent Android Studio.
- Run the
appconfiguration 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โ asealed interfaceof states (Initial, feature states,Error).{Feature}ViewModelโ a@HiltViewModelexposing a singleStateFlow; no
Android/lifecycle references.{Feature}Controllerโ a@Stablestate-holder that owns the camera SDK lifecycle, created
with aremember{Feature}Controller(...)composable.{Feature}Screenโ collects state withcollectAsStateWithLifecycle, wraps everything in the
shared scaffold, and renders with awhen(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 theAISampleCatalogThemewrapper.:core-cameraโ camera plumbing:BaseCamera2Controller(background thread, open/close,
viewfinder transform, tap-to-focus, session creation),Camera2Preview/CameraXPreview,ImageUtils(Image/ImageProxyโBitmap),MediaStoreSaver,rememberDisplayRotation(),
andCameraPermissions. It re-exports the common CameraX/Camera2 libraries.:core-uiโ API-agnostic Compose chrome:CameraSampleScaffold(permission flow + surface),
the viewfinder HUD (FocusIndicatorreticle,RuleOfThirdsGrid,ViewfinderTitleChip,TorchChip),ShutterButton/RecordButton/CameraControlsBar/ScrimIconButton,ValueSlider/ZoomControls,CapturedImagePreview/CapturedVideoPreview/VideoPlayer,
theSettingsOverlaymenu, and the loading / error / unsupported state views. It re-exports:core-cameraand: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:
./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 filesThen 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
- Viewfinder. CameraX previews use
CameraXViewfinder; Camera2 previews use the ComposeViewfinder. Never usePreviewView,ViewfinderView,SurfaceView, orTextureView.
In practice always render through the sharedCameraXPreview/Camera2Previewcomposables
(:core-camera) โ there are no Android Views in sample/UI code. - 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). - No loose strings. Every user-facing string lives in a module's
res/values/strings.xmland is
read viastringResource(...)/context.getString(...). No string literals inText(...),contentDescription, button labels, etc. - Reuse the shared layers (
:core-camera,:core-ui,:core-theme). Don't reinvent shutter
buttons, control bars, scaffolds, previews, sliders, overlays, theme tokens, etc. - Layered architecture (see below):
UiStateโViewModelโControllerโScreen.
ViewModels never referenceContext,Activity, lifecycle, orResources. - Guard optional hardware โ render
UnsupportedViewinstead of crashing. - Apache license header on every file; run
./gradlew spotlessApply(ktlint + headers)
before finishing.
Repository layout / module graph
: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-uiDependency 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 fromspotless/copyright.kt..editorconfigsetsktlint_function_naming_ignore_when_annotated_with = Composableso PascalCase@Composablenames
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โ asealed interfaceof states:Initial, one or more feature states
(e.g.Previewing,Capturing,Recording,Captured), andError(message). AddUnsupported
when hardware support is conditional.{Feature}ViewModelโ@HiltViewModel,@Inject constructor. Exposes exactly oneval uiState: StateFlow<{Feature}UiState>(a privateMutableStateFlowsurfaced immutably).
NoContext/Activity/lifecycle/Resources; no events pushed to the UI (process the
event, update state). Business logic lives here.{Feature}Controllerโ a@Stablestate-holder that owns the camera SDK lifecycle
(open/close, sessions, capture/record). Created from the Screen via aremember{Feature}Controller(...)
composable. Camera2 controllers extendBaseCamera2Controller; CameraX controllers wrapProcessCameraProvider. The controller is the only place that holdsContext.{Feature}Screenโ the screen-level composable. Collects withcollectAsStateWithLifecycle(),
wraps content inCameraSampleScaffold(...), and branches withwhen (state).
Conventions
- Lifecycle-aware collection (
collectAsStateWithLifecycle); never override Activity lifecycle methods
โ useLifecycleEventObserver+DisposableEffect. - Every reusable composable (not the top-level Screen) takes
modifier: Modifier = Modifieras its
first optional parameter. - Single-activity app + Navigation-Compose; the
NavHostis derived fromsampleCatalog. - 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
sharedelse ->branch), not separately perwhenbranch โ 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 โ
@HiltAndroidAppApplication,@AndroidEntryPointMainActivity, ViewModels viahiltViewModel(...).
The viewfinder rule (expanded)
- CameraX โ render the
SurfaceRequestthroughCameraXPreview(:core-camera), which usesandroidx.camera.compose.CameraXViewfinder. - Camera2 โ render through
Camera2Preview(:core-camera), which usesandroidx.camera.viewfinder.compose.Viewfinderdriven by aBaseCamera2Controller. - Never instantiate
PreviewView,ViewfinderView,SurfaceView, orTextureView. Even the
captured-video player is Compose (Media3PlayerSurface), not aVideoView.
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.xmlwith keys{api}_{feature}_list_titleand{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 (
:appcatalog/ui): mono header + live count, theSampleCategoryfilter pills
(All ยท Images ยท Video ยท ML ยท Graphics ยท Extensions ยท Controls), a bordered featured hero
(CatalogWideCard), and a 2-column grid of compact tiles (CatalogRowCard) grouped underCAMERAX
/CAMERA2section 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 squareFocusIndicatorreticle, a bottomCameraControlsBarwithShutterButton/RecordButton,TorchChip/TorchGlow, andZoomControls. - 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 bySampleTags).Color.kt,Theme.kt displayFontFamily,bodyFontFamily(Space Grotesk),monoFontFamily(Space Mono),AppTypography.Type.kt
`:core-camera` (`com.android.camera.core.*`)
BaseCamera2Controllerโ@Stableabstract base: background-thread open/close, session creation
(createCaptureSession), display-rotation transform, tap-to-focus (focus),surfaceRequest/transformationInfostate,release(); subclasses overrideonCameraOpened/configureOutput/ etc.camera2/BaseCamera2Controller.ktCamera2Preview(controller, modifier, onFocusTap)โ ComposeViewfinderhost.camera2/Camera2Preview.ktCameraXPreview(surfaceRequest, modifier, onTapToFocus, onFocusTap)โCameraXViewfinderhost
with coordinate transform.camerax/CameraXPreview.ktImageProxy.toBitmap(mirror)/Image.toBitmap(sensorOrientation, mirror)โ JPEG โ orientedBitmap.image/ImageUtils.ktMediaStoreSaverโsaveBitmap(...)to DCIM/Camera,newVideoFile(),scanFile(...).media/MediaStoreSaver.ktCameraPermissionsโPHOTO,VIDEOpermission lists.permissions/CameraPermissions.ktrememberDisplayRotation(): 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);
CameraApienum (CAMERA2,CAMERAX).
- API badge);
- 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 Media3PlayerSurface; 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; theNavHostand 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
- Generate:This scaffoldsbash
./gradlew createSample \ -PsampleName="camera2-flash" \ -PscreenName="Camera2FlashScreen" \ -Ptitle="Camera2 โข Flash" \ -Pdesc="Toggle the flash with Camera2" \ -Ptype="camera2" # camera2 (default) | cameraxsamples/camera2-flash/(UiState / ViewModel / Controller / Screen +build.gradle.ktsstrings.xml) and wiressettings.gradle.kts,app/build.gradle.kts, the catalog
(SampleCatalog.kt), and the catalog strings.
./gradlew spotlessApply.- Implement the feature in the generated
Controller+Screen; add in-screen strings to the
module'sstrings.xml; set the newSampleCatalogItem'scategory(andtags/isFeaturedif
relevant). - Honor every TL;DR rule โ reuse
:core-ui/:core-camera, render viaCameraXPreview/Camera2Preview, no loose strings, one feature. - 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
datapackage 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
uipackage 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
repeatOnLifecyclein the View system andcollectAsStateWithLifecyclein Jetpack Compose. In your composable, you can use a when expression to handle the state as follows:
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
Modifieras 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
suspendfunctions withviewModelScopefor 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:
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 theViewModelclass. Avoid using theApplicationclass 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
uiStateproperty (aStateFlow) for data exposure. Multiple properties can be used for unrelated data. UsestateInwithWhileSubscribed(5000)for data streams. For simpler cases, use aMutableStateFlowexposed as an immutableStateFlow. Consider using a data class or sealed class for theUiState. - Recommeded: Donโt pass
Contextto yourViewModel. To avoid memory leaks only UI (Composables) should have a reference tocontext. - Recommeded: Donโt use
fetchData()in a ViewModelinit {}block. If you do end up in a case where you need to do something on initial load, you usestateIn()instead.
Lifecycle
Best practices for working with the Android lifecycle:
- Strongly recommended: Do not override lifecycle methods (e.g.,
onResume) in Activities or Fragments. UseLifecycleObserverinstead. For lifecycle-dependent work, userepeatOnLifecycle.
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
valueproperty when possible. Create acollectJobwhen usingWhileSubscribed.
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
Defaultif no better name is found (e.g.,OfflineFirstNewsRepository,InMemoryNewsRepository,DefaultNewsRepository). Prefix fake implementations withFake(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:
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:
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:
- 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.
- 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.
- 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.
- Include error logs from logcat, if there are any.
- 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
Copyright (C) 2020 The Android Open Source Project
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
- 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