Skip to content

Fix Android camera selection using index-based stable IDs#279

Merged
jfversluis merged 8 commits intomainfrom
copilot/fix-android-camera-selection
Dec 18, 2025
Merged

Fix Android camera selection using index-based stable IDs#279
jfversluis merged 8 commits intomainfrom
copilot/fix-android-camera-selection

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Nov 4, 2025

Camera Selection Fix for Android

Problem

On Android, camera selection fails because CameraSelector.ToString() returns non-stable hash-based identifiers. This prevents cameras from being matched correctly when selected, causing all selections to revert to the default rear camera.

Solution - Index-Based Stable IDs (No Interop Required)

Instead of using Camera2 interop (which has dependency issues), this fix uses a simpler approach:

  • Camera IDs are based on lens facing and enumeration order: "front-0", "rear-0", "rear-1", "rear-2", etc.
  • These IDs are stable as long as camera enumeration order is consistent (which it is in CameraX)
  • No additional dependencies required - uses only the existing AndroidX CameraX API

Latest Fix

Fixed camera selection not working by using a local selectedCameraSelector variable instead of directly modifying the _cameraSelector field. This ensures the camera selector is only updated after successfully finding the target camera, preventing state corruption when camera selection fails.

Changes

CameraManager.android.cs:

  • GetAvailableCameras(): Creates stable camera IDs in format "front-{index}" or "rear-{index}" and generates descriptive names
  • UpdateCamera():
    • Uses local variable selectedCameraSelector to build camera selection logic
    • Only updates _cameraSelector field after successfully finding the target camera
    • Parses the DeviceId to extract facing and index, then matches the camera by iterating through cameras with the same facing

CAMERA_SELECTION.md:

  • Updated Android platform notes to reflect the index-based ID approach

Expected Behavior

Before (Galaxy S25 with 3 rear + 1 front camera):

1. Rear (Camera 0 (Rear))
2. Front (Camera 1 (Front))  
3. Rear (Camera 2 (Rear))
4. Front (Camera 3 (Front))  // Wrong - should be Rear

After:

1. Front Camera (ID: "front-0")
2. Rear Camera (ID: "rear-0")
3. Rear Camera 2 (ID: "rear-1")
4. Rear Camera 3 (ID: "rear-2")

Selected cameras now persist correctly across sessions and restore from saved preferences.

Technical Notes

  • Camera enumeration order in AndroidX CameraX is stable and deterministic
  • IDs are human-readable and predictable
  • No external dependencies or interop required
  • Backward compatible approach using only existing APIs
  • Fixed state corruption issue by using local variable before updating field
Original prompt

This section details on the original issue you should resolve

<issue_title>0.6.0 – On Android, selecting a camera always defaults to the rear camera, while it works correctly on iOS.</issue_title>
<issue_description>I’m using a barcode scanner inside a popup from CommunityToolkit.Maui. In the center of the popup, I’ve added a button that allows the user to manually select a camera. The app lists all available cameras, and the user can choose one and save it as their default camera.

This works perfectly on iOS, but on Android I’m getting an incorrect list of cameras. For example, on my Galaxy S25, which has three rear cameras and one front camera, the library reports two front and two back cameras instead. Additionally, no matter which camera I select, it always switches back to the rear camera.

Below is the list of cameras returned, followed by my implementation:

Image

MauiBarcodeScannerPage.xaml

<toolkit: Popup
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:system="clr-namespace:System;assembly=mscorlib"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;assembly=ZXing.Net.MAUI.Controls"
    xmlns:zxingoptions="clr-namespace:ZXing.Net.Maui;assembly=ZXing.Net.MAUI"
    x:TypeArguments="system:String"
    x:Class="BIZS5.MauiPages.MauiBarcodeScannerPage"
    CanBeDismissedByTappingOutsideOfPopup="True"
    HorizontalOptions="Center"
    VerticalOptions="Center"
    BackgroundColor="Transparent"
    WidthRequest="{OnPlatform iOS={OnIdiom Phone=320,Tablet=500}, WinUI=600, Android={OnIdiom Phone=300, Tablet=500}, Default=-1}"
    HeightRequest="{OnPlatform iOS={OnIdiom Phone=320,Tablet=500}, WinUI=450, Android={OnIdiom Phone=400, Tablet=500}, Default=-1}">

    <AbsoluteLayout>

        <!-- Fullscreen camera -->
        <zxing:CameraBarcodeReaderView
                x:Name="scanner"
                AbsoluteLayout.LayoutBounds="0,0,1,1"
                AbsoluteLayout.LayoutFlags="All"
                IsDetecting="false"
                CameraLocation="Rear"
                BarcodesDetected="scanner_BarcodesDetected">
            <zxing:CameraBarcodeReaderView.Options>
                <zxingoptions:BarcodeReaderOptions UseCode39ExtendedMode="True" AutoRotate="True"/>
            </zxing:CameraBarcodeReaderView.Options>
            <zxing:CameraBarcodeReaderView.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnPreviewTapped"/>
            </zxing:CameraBarcodeReaderView.GestureRecognizers>
        </zxing:CameraBarcodeReaderView>

        <!-- Bottom-left -->
        <Button Text="🔄️"
                    Clicked="SwitchCameraButton_Clicked"
                    BackgroundColor="#66000000"
                    CornerRadius="10"
                    Padding="10"
                    AbsoluteLayout.LayoutBounds="0,1,AutoSize,AutoSize"
                    AbsoluteLayout.LayoutFlags="PositionProportional"
                    Margin="16"/>
        <!-- Bottom-center -->
        <Button Text="📷"
                Clicked="SelectCameraButton_Clicked"
                BackgroundColor="#66000000"
                CornerRadius="10"
                Padding="10"
                AbsoluteLayout.LayoutBounds="0.5,1,AutoSize,AutoSize"
                AbsoluteLayout.LayoutFlags="PositionProportional"
                Margin="16"/>


        <!-- Bottom-right -->
        <Button Text="💡"
                    Clicked="TorchButton_Clicked"
                    BackgroundColor="#66000000"
                    CornerRadius="10"
                    Padding="10"
                    AbsoluteLayout.LayoutBounds="1,1,AutoSize,AutoSize"
                    AbsoluteLayout.LayoutFlags="PositionProportional"
                    Margin="16"/>
    </AbsoluteLayout>
</toolkit: Popup>

MauiBarcodeScannerPage.xaml.cs

using CommunityToolkit.Maui.Views;
using ZXing.Net.Maui;
using ZXing.Net.Maui.Controls;
using BIZS5.Services;
using Microsoft.Maui.ApplicationModel;

namespace BIZS5.MauiPages;

public partial class MauiBarcodeScannerPage : Popup<string> {
    private readonly AppConfigService _appConfig;
    private List<CameraInfo> _cachedCameras = new();

    public MauiBarcodeScannerPage(AppConfigService? appConfig = null) {
        InitializeComponent();

        _appConfig = appConfig ?? TryResolve<AppConfigService>()
            ?? throw new InvalidOperationException("AppConfigService not available.");

        Opened += async (_, __) => {
            await Task.Delay(250);
            await InitializeCameraSelectionAsync();
            scanner.IsDetecting = true;
        };

        Closed += (_, __) => scanner.IsDetecting = false;
    }

    private static T? TryResolve<T>() where T : class {
        var services =
            Application.Current?.Handler?.MauiContext?...

</details>

- Fixes Redth/ZXing.Net.Maui#278

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

Copilot AI self-assigned this Nov 4, 2025
Copilot AI and others added 4 commits November 4, 2025 10:53
Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
…pes and debug logging

Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix camera selection defaulting to rear on Android Fix Android camera selection using Camera2 interop for stable IDs Nov 4, 2025
Copilot AI requested a review from jfversluis November 4, 2025 11:04
@jfversluis
Copy link
Copy Markdown
Collaborator

@copilot build error: D:\a\ZXing.Net.Maui\ZXing.Net.Maui\ZXing.Net.MAUI\Platforms\Android\CameraManager.android.cs(13,31): error CS0234: The type or namespace name 'Interop' does not exist in the namespace 'AndroidX.Camera.Camera2' (are you missing an assembly reference?) [D:\a\ZXing.Net.Maui\ZXing.Net.Maui\ZXing.Net.MAUI\ZXing.Net.MAUI.csproj::TargetFramework=net9.0-android]

Isnt there a better way to do this without using interop?

Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Nov 4, 2025

@copilot build error: D:\a\ZXing.Net.Maui\ZXing.Net.Maui\ZXing.Net.MAUI\Platforms\Android\CameraManager.android.cs(13,31): error CS0234: The type or namespace name 'Interop' does not exist in the na...

You're right - Camera2 interop isn't available in the current AndroidX.Camera.Camera2 package version. I've changed the approach to use index-based stable IDs instead (format: "front-0", "rear-0", "rear-1", etc.) which only requires the existing CameraX API. This is simpler, has no dependency issues, and the IDs remain stable as long as camera enumeration order is consistent (which it is in CameraX). Fixed in commit 0b6f428.

Copilot AI changed the title Fix Android camera selection using Camera2 interop for stable IDs Fix Android camera selection using index-based stable IDs Nov 4, 2025
@hwmoolmanDM
Copy link
Copy Markdown

hwmoolmanDM commented Dec 4, 2025

Hi @jfversluis, I needed this functionality for a project I am working on, and I have found part of the issue, and part of the solution.

After cloning the branch, I still had the same issue as was reported initially - on a Samsung A35 with three rear cameras and one front camera, it reported two rear and two front cameras. When these cameras are selected, the two front facing options both activate the front camera, but the two rear facing options activate the "main" camera and wide angle camera.

After investigating this further, it seems Samsung only exposes 2 of the 3 rear cameras, (normal and wide angle, not the telephoto), and for some reason there are two entries of the front facing camera?

I did manage to fix the camera selection not working, by changing UpdateCamera() to use a local selectedCameraSelector variable instead of directly modifying _cameraSelector field. The commit is on my local branch, you can let me know if you want me to push the change.

@jfversluis
Copy link
Copy Markdown
Collaborator

@copilot please look at #279 (comment) and apply that to this PR

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Dec 8, 2025

@copilot please look at #279 (comment) and apply that to this PR

I'm unable to access the referenced comment at #279 (comment) due to network restrictions. Could you please share the specific changes or code that needs to be applied from that comment?

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/Redth/ZXing.Net.Maui/issues/279/comments/3612546472
    • Triggering command: /usr/bin/curl curl -s -H Accept: application/vnd.github.v3&#43;json REDACTED (http block)
  • https://api.github.com/repos/Redth/ZXing.Net.Maui/pulls/279
    • Triggering command: /usr/bin/curl curl -s REDACTED (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@jfversluis
Copy link
Copy Markdown
Collaborator

@copilot here is the comment inline from a user on this PR, cn you please apply this suggestion to this PR?

Hi @jfversluis, I needed this functionality for a project I am working on, and I have found part of the issue, and part of the solution.

After cloning the branch, I still had the same issue as was reported initially - on a Samsung A35 with three rear cameras and one front camera, it reported two rear and two front cameras. When these cameras are selected, the two front facing options both activate the front camera, but the two rear facing options activate the "main" camera and wide angle camera.

After investigating this further, it seems Samsung only exposes 2 of the 3 rear cameras, (normal and wide angle, not the telephoto), and for some reason there are two entries of the front facing camera?

I did manage to fix the camera selection not working, by changing UpdateCamera() to use a local selectedCameraSelector variable instead of directly modifying _cameraSelector field. The commit is on my local branch, you can let me know if you want me to push the change.

Co-authored-by: jfversluis <939291+jfversluis@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Dec 8, 2025

@copilot here is the comment inline from a user on this PR, cn you please apply this suggestion to this PR?

Hi @jfversluis, I needed this functionality for a project I am working on, and I have f...

Applied the fix to use a local selectedCameraSelector variable in UpdateCamera() instead of directly modifying the _cameraSelector field. This prevents state corruption when camera selection fails and ensures the camera selector is only updated after successfully finding the target camera. Fixed in commit 63b4977.

@jfversluis
Copy link
Copy Markdown
Collaborator

@hwmoolmanDM Could you try the latest artifacts please?

@jfversluis
Copy link
Copy Markdown
Collaborator

@hwmoolmanDM if you could let me know that would be amazing!

@jfversluis jfversluis marked this pull request as ready for review December 16, 2025 15:33
@jfversluis jfversluis requested a review from Copilot December 16, 2025 15:33
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes camera selection on Android by implementing stable, index-based camera IDs instead of relying on unstable hash-based identifiers from CameraSelector.ToString(). The fix ensures that selected cameras persist correctly across sessions and properly handles devices with multiple cameras of the same facing direction.

Key Changes:

  • Introduced stable camera ID format ("front-{index}", "rear-{index}") based on lens facing and enumeration order
  • Fixed state corruption issue by using a local variable before updating the _cameraSelector field
  • Updated camera selection logic to parse stable IDs and match cameras by facing direction and index

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
ZXing.Net.MAUI/Platforms/Android/CameraManager.android.cs Implements stable camera ID generation and fixes camera selection logic with local variable pattern
CAMERA_SELECTION.md Updates documentation to reflect the new index-based stable ID approach

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Anawaz
Copy link
Copy Markdown

Anawaz commented Dec 18, 2025

I think we should merge this in next version. With this code atleast switching cameras are working.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@jfversluis jfversluis merged commit a2677e0 into main Dec 18, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants