Android Camera Samples

repository·main·Indexed 26 days ago

https://github.com/android/camera-samples

A catalog of Compose-first Android application samples demonstrating the use of Camera2 and CameraX APIs. The repository includes shared scaffolding for camera plumbing, UI components, and architectural guidance on implementing Unidirectional Data Flow (UDF) and layered architecture. Featured samples include 10-bit HDR video capture via Camera2HdrVideoController and multimodal prompt generation using the Gemini generative model via the Firebase AI SDK.

Tokens
4.3K
Snippets
6
Records
24
Agent score
90%

What's inside android-camera-samples

  1. Architecture of camera samples

    main

    Each sample is a Gradle library module located under samples/{api}-{feature}/ (package com.android.{api}.{feature}). Samples follow a layered, unidirectional pattern:

    • {Feature}UiState: A sealed interface representing states like Initial, specific feature states, or Error.
    • {Feature}ViewModel: A @HiltViewModel that exposes a single StateFlow<UiState>. It contains no Android or lifecycle references.
    • {Feature}Controller: A @Stable state-holder that manages the camera SDK lifecycle. It is instantiated using remember{Feature}Controller(...).
    • {Feature}Screen: A Composable that collects state using collectAsStateWithLifecycle, wraps the content in the shared scaffold, and renders the UI based on the current state using a when(state) block.
  2. Handle Android Lifecycle and Dependencies

    main

    Lifecycle

    • Do not override lifecycle methods like onResume in Activities or Fragments. Use LifecycleObserver or repeatOnLifecycle for lifecycle-dependent work.

    Dependency Injection

    • Use constructor injection as the primary method.
    • Use Hilt for complex projects (multiple ViewModels, WorkManager, advanced Navigation) or manual DI for simpler apps.
  3. Best practices for ViewModels

    main

    ViewModels should manage UI state and provide access to the data layer while remaining agnostic of the Android lifecycle.

    • Lifecycle Agnosticism: Do not hold references to Activities, Fragments, Context, or Resources. Never pass an Activity as a parameter to a ViewModel function.
    • State Management: Expose a single UI state using a StateFlow. Use a sealed class to represent the different states of a screen.
    • Scope: Use viewModelScope for actions and interact with layers using suspend functions and Flows.
    • Avoid AndroidViewModel: Use the standard ViewModel class and avoid using the Application class within it.
    • Initialization: Do not use fetchData() inside an init {} block; use stateIn() instead for initial data loading.
    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
    }
  4. Add a new camera sample

    main

    The project provides a Gradle task to scaffold a new, working (preview-only) Compose-first module. This generator creates the module, wires it into the build system, and adds it to the catalog.

    Use the createSample task with the following parameters:

    • -PsampleName: The name of the sample module.
    • -PscreenName: The name of the Composable screen class.
    • -Ptitle: The display title for the sample.
    • -Pdesc: A description of the sample.
    • -Ptype: The API type, either camera2 (default) or camerax.

    After generating, implement your feature logic within the generated Controller and Screen classes, then re-sync Gradle. Finally, run ./gradlew spotlessApply to format the new files.

    ./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
  5. Run the Android Camera Samples Catalog

    main

    To run the catalog, follow these steps:

    1. Clone the repository.
    2. Open the project in a recent version of Android Studio.
    3. Run the app configuration on a physical device or an emulator.

    Note: While emulators with a virtual camera can cover basic functionality, camera-dependent samples (such as Extensions, Slow Motion, or ML) perform best on a physical device. No Firebase or google-services.json is required.

  6. Implement Unidirectional Data Flow (UDF) in the UI Layer

    main
    Follow the Unidirectional Data Flow pattern where ViewModels expose UI state via the observer pattern and receive actions through method calls. Avoid sending events from the ViewModel to the UI; instead, update the state in the ViewModel and let the UI observe that state change.
  7. Implement a Layered Architecture

    main

    To improve scalability and maintainability, use a layered architecture that promotes separation of concerns and follows Unidirectional Data Flow (UDF).

    • Data Layer: Exposes application data and contains business logic. Always use a Repository to mediate between the UI and data sources (databases, network, etc.). UI components should never interact with data sources directly.
    • UI Layer: Displays data and handles user interaction. In small apps, place these in a ui package or module.
    • Domain Layer (Large Apps): Use use cases for reusable business logic across multiple ViewModels.
    • Communication: Use Kotlin Coroutines and Flows for inter-layer communication.
  8. Testing Best Practices

    main

    Focus testing efforts on ViewModels (including Flows), data layer entities (repositories and data sources), and UI navigation for regression testing.

    • Test Doubles: Prefer fakes over mocks.
    • StateFlow Testing: Assert on the value property of StateFlows when possible. If using WhileSubscribed, create a collectJob to manage the collection during tests.
  9. Collect UI State in Jetpack Compose

    main

    Use lifecycle-aware state collection to ensure resources are managed correctly. In Jetpack Compose, use collectAsStateWithLifecycle(). You can then use a when expression to handle different states defined by a sealed class.

    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
        }
    }
  10. Initialize the Gemini generative model

    main

    The sample uses the Firebase AI SDK for Android to interact with the gemini-2.5-flash model. The model is initialized using Firebase.ai(backend = GenerativeBackend.googleAI()). You can configure the generationConfig (temperature, topK, topP, maxOutputTokens) and safetySettings (HarmCategory and HarmBlockThreshold) during initialization.

    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),
            ),
        )
    }