Typing thirteen digits from a book is a small but recurring source of errors. A camera can decode the barcode; the application must still validate it as an ISBN and transfer it to the correct form exactly once. The Kitaplık archive contains ZXing integration pieces, but the connection from the detected value to the existing ISBN field is incomplete. This independent guide fills that boundary explicitly.
Separate the observed archive flow from the proposed completion
The inspected code references ZXing.Net.Maui.Controls 0.4.0, registers UseBarcodeReader and contains CameraBarcodeReaderView with BarcodesDetected. The detected value is dispatched to a ViewModel, detection stops and navigation opens BookAdd. BookAddViewModel already has an ISBN property and bound Entry. However, BarcodeFound does not assign its value to that field, so the complete “scan and populate” behavior cannot be claimed from the archive alone.
The example targets the existing page and its ViewModel instead of constructing a replacement form. It assumes the appropriate MAUI workload, platform tooling and package setup. The API shape follows the archive's 0.4.0 context; newer MAUI workloads may require adjustments to changed or deprecated UI APIs.
Registering a camera library is not user permission
// MauiProgram.cs — after UseMauiApp<App>()
builder.UseMauiApp<App>().UseBarcodeReader();
// AndroidManifest.xml — inside manifest
<uses-permission android:name="android.permission.CAMERA" />
// Platforms/iOS/Info.plist — inside dict
<key>NSCameraUsageDescription</key>
<string>Kitap barkodunu okuyarak ISBN alanını doldurmak için kamera kullanılır.</string>
UseBarcodeReader registers the integration. Android manifest permission and the iOS usage description establish platform declarations, not a runtime permission grant. Request the permission and inspect its result. Manual ISBN entry remains a useful path when the user declines access.
The page can disappear while the permission dialog is open. Resuming the await must not activate a scanner on an obsolete page. The complete example therefore tracks a session number as well as whether the page is active. A late permission result cannot apply to a different appearance session.
Start with detection disabled
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;assembly=ZXing.Net.MAUI.Controls"
x:Class="BarcodeLesson.ScanPage">
<Grid>
<zxing:CameraBarcodeReaderView x:Name="Reader"
IsDetecting="False"
BarcodesDetected="OnBarcodesDetected" />
</Grid>
</ContentPage>
IsDetecting begins false and becomes true only after permission and the current page session are accepted. Restricting formats to Ean13 reduces unrelated barcode results, but an EAN-13 result is not automatically an ISBN. The format is used for other products as well.
Validate the book-input contract
public static class Isbn13
{
public static bool TryNormalize(string? raw, out string isbn)
{
isbn = (raw ?? "").Replace("-", "").Replace(" ", "");
if (isbn.Length != 13 || isbn.Any(c => c < '0' || c > '9')) return false;
if (!isbn.StartsWith("978", StringComparison.Ordinal) &&
!isbn.StartsWith("979", StringComparison.Ordinal)) return false;
// 979-0 belongs to printed music (ISMN), not this book workflow.
if (isbn.StartsWith("9790", StringComparison.Ordinal)) return false;
int sum = 0;
for (int i = 0; i < 12; i++)
sum += (isbn[i] - '0') * (i % 2 == 0 ? 1 : 3);
return (10 - sum % 10) % 10 == isbn[12] - '0';
}
}
TryNormalize removes hyphens and ordinary spaces, requires thirteen ASCII digits, checks the 978/979 prefix and excludes the 979-0 music-publication range from this book workflow. The first twelve digits receive alternating weights of one and three. The final digit is computed as (10 − sum mod 10) mod 10. For 9780306406157, the weighted sum is 93 and the expected final digit is seven.
A matching checksum does not establish that an ISBN was actually allocated or that a catalog can identify it. A valid-looking number may have no record in the chosen metadata source. The barcode should not be used to invent a title or author; a separate catalog lookup can propose metadata for the user to review.
ISBN-10 is outside this helper's accepted format. Supporting it needs an explicit additional conversion policy. The normalization is intentionally narrow too: it does not remove every Unicode whitespace character or repair arbitrary malformed input.
One barcode can produce many callbacks
The camera processes a sequence of frames. A book held in the same position can be detected repeatedly. Multiple=false limits results within a detection context; it does not guarantee a single event for the page's lifetime. Switching IsDetecting off after a callback may still leave already queued callbacks.
Interlocked.CompareExchange allows only one callback to change armed from one to zero. Other callbacks return before scheduling UI work. The main-thread action then checks active and session again, preventing a result from an obsolete page appearance from affecting the current form.
using ZXing.Net.Maui;
using ZXing.Net.Maui.Controls;
namespace BarcodeLesson;
public partial class ScanPage : ContentPage
{
private int _armed;
private int _session;
private bool _active;
private readonly Action<string> _acceptIsbn;
// Caller supplies the existing book form's ISBN setter.
public ScanPage(Action<string> acceptIsbn)
{
InitializeComponent();
_acceptIsbn = acceptIsbn;
Reader.Options = new BarcodeReaderOptions
{
Formats = BarcodeFormat.Ean13,
AutoRotate = true,
Multiple = false
};
}
protected override async void OnAppearing()
{
base.OnAppearing();
_active = true;
var session = Interlocked.Increment(ref _session);
try
{
var status = await Permissions.RequestAsync<Permissions.Camera>();
if (!_active || session != Volatile.Read(ref _session)) return;
if (status != PermissionStatus.Granted)
{
await DisplayAlert("Kamera", "İzin verilmedi; ISBN'yi elle girebilirsin.", "Tamam");
await Navigation.PopAsync();
return;
}
Interlocked.Exchange(ref _armed, 1);
Reader.IsDetecting = true;
}
catch (Exception)
{
if (_active && session == Volatile.Read(ref _session))
await DisplayAlert("Kamera", "Kamera başlatılamadı; elle giriş kullan.", "Tamam");
}
}
private void OnBarcodesDetected(object? sender, BarcodeDetectionEventArgs e)
{
var session = Volatile.Read(ref _session);
var raw = e.Results?.FirstOrDefault()?.Value;
if (string.IsNullOrWhiteSpace(raw) ||
Interlocked.CompareExchange(ref _armed, 0, 1) != 1) return;
MainThread.BeginInvokeOnMainThread(async () =>
{
if (!_active || session != Volatile.Read(ref _session)) return;
Reader.IsDetecting = false;
if (!Isbn13.TryNormalize(raw, out var isbn))
{
await DisplayAlert("Barkod", "Geçerli bir ISBN-13 okunmadı.", "Tekrar tara");
if (_active && session == Volatile.Read(ref _session))
{
Interlocked.Exchange(ref _armed, 1);
Reader.IsDetecting = true;
}
return;
}
try
{
_acceptIsbn(isbn);
await Navigation.PopAsync();
}
catch (Exception)
{
if (_active && session == Volatile.Read(ref _session))
await DisplayAlert("Kitap", "ISBN aktarımı tamamlanamadı. Geri dönüp elle girebilirsin.", "Tamam");
}
});
}
protected override void OnDisappearing()
{
_active = false;
Interlocked.Increment(ref _session);
Interlocked.Exchange(ref _armed, 0);
Reader.IsDetecting = false;
base.OnDisappearing();
}
}
For an invalid ISBN, the scan is rearmed only while the same session remains active. For a valid result, the gate prevents repeated field assignment and duplicate PopAsync calls. The atomic gate and session check protect different races and should not be treated as interchangeable flags.
Return to the existing BookAdd ViewModel
// In the existing BookAdd page, whose BindingContext is BookAddViewModel:
private async void OpenScanner(object sender, EventArgs e)
{
var vm = (BookAddViewModel)BindingContext;
await Navigation.PushAsync(new BarcodeLesson.ScanPage(isbn => vm.ISBN = isbn));
}
// BookAdd.xaml: <Entry Text="{Binding ISBN}" Placeholder="ISBN" />
// BookAddViewModel: [ObservableProperty] private string _ISBN = "";
Passing Action<string> keeps the scanner independent of the BookAddViewModel type. The caller assigns its own existing ViewModel's ISBN property rather than opening another form instance. Previously entered title or notes can therefore remain intact. The generated observable property notifies the bound Entry of the new value.
This is a NavigationPage PushAsync/PopAsync example. A Shell application needs a navigation and result contract appropriate to Shell rather than mixing the two models blindly. Assigning the ISBN does not create a book record; the user still reviews the form and submits it separately.
Field assignment may succeed even if the subsequent navigation fails. An error at that point should not automatically trigger another book-creation request. Setting an ISBN and persisting a book are different side effects. Any duplicate-book policy still needs enforcement at the server's save boundary.
Check resource lifetime as well as detection state
OnDisappearing invalidates the session, closes the gate and disables detection. IsDetecting controls decoding; it is not proof that every preview or platform handler resource has been released. Back navigation, backgrounding and reopening should be observed on real devices against the selected package version.
The async void methods here are at MAUI lifecycle and event boundaries whose signatures require that form. General services should return Task so errors and cancellation have an owner. Moving camera callback UI changes through MainThread is another part of respecting that boundary.
Separate pure validation tests from device checks
Isbn13 can be tested without a camera using a valid number, bad checksum, short input, letters, hyphenated text and a 979-0 value. The event flow needs repeated callbacks, navigation during permission requests and stale-session results. Device checks then cover denied permission, orientation, lighting, incomplete barcodes and re-entry.
This guide does not claim a completed physical-camera test. It documents source observations and an independent integration example. The intended user flow is concrete: scan when permission is granted, populate the existing form once for an accepted ISBN, retain manual entry when scanning is unavailable, and save the book only through the normal reviewed form action.
ZXing.Net.Maui sources; MAUI permissions; International ISBN Agency.