Kitabın arkasındaki on üç rakamı elle girmek küçük ama tekrar eden bir hata kaynağı. Kamera barkodu okuyabilir; asıl uygulama işi, o değerin ISBN olduğunu doğrulayıp doğru form örneğine bir kez aktarmaktır. Kitaplık arşivinde ZXing entegrasyonunun parçaları var, fakat okunan değerin mevcut ISBN alanına atanmasında eksik bir bağlantı bulunuyor. Bu rehber o sınırı tamamlayan bağımsız bir örnek üzerinden ilerliyor.
Arşivdeki akış ile rehberin eklediği akış
İncelenen kodda ZXing.Net.Maui.Controls 0.4.0, UseBarcodeReader kaydı, CameraBarcodeReaderView ve BarcodesDetected olayı bulunuyor. Olay değeri Dispatcher üzerinden ViewModel'e iletiliyor; algılama durdurulup BookAdd ekranına geçiliyor. BookAddViewModel'de ISBN özelliği ve ona bağlı Entry var. Ancak BarcodeFound içindeki value mevcut alana aktarılmadığından “kamera gördü, form doldu” uçtan uca sonucu kaynak koddan doğrulanamıyor.
Aşağıdaki örnek bu nedenle var olan sayfayı ve onun ViewModel'ini hedefliyor. Paket ve MAUI hedef platformu birlikte kurulmalı; yalnız NuGet referansı kamera cihazını hazır hale getirmez. API biçimi arşivde görülen 0.4.0 bağlamını esas alıyor. Yeni MAUI iş yüklerinde değişen veya kullanım dışı işaretlenen UI API'leri kurulu sürümle eşleştirilmeli.
Kamera kaydı ve kullanıcı izni farklı adımlar
// 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, kütüphanenin MAUI tarafındaki kaydını yapar. Android manifest bildirimi ve iOS kullanım açıklaması platform şartlarıdır. Bunların dosyada bulunması kullanıcının çalışma anında izin verdiği anlamına gelmez. Permissions.RequestAsync ile izin sonucu kontrol edilir; reddeden kullanıcı için elle ISBN girişi kullanılabilir kalır.
İzin penceresi açıkken kullanıcı sayfadan ayrılabilir. await tamamlandığında artık görünmeyen sayfada kamerayı başlatmamak gerekir. Bu yüzden aşağıdaki tam sayfada yalnız bool değil, her görünme oturumu için session numarası da kullanıyorum. Eski izin cevabı yeni sayfa durumuna uygulanmıyor.
Önizleme görünümü, başlangıçta kapalı algılama
<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 başlangıçta false. İzin ve geçerli sayfa oturumu doğrulanınca true yapılır. Örnekte Ean13 formatına odaklanılması gereksiz QR veya başka barkod sonuçlarını azaltır; fakat EAN-13 okunması tek başına ISBN bulunduğu anlamına gelmez. Aynı format pek çok ürün kodunda kullanılır.
On üç rakamın ISBN akışına kabul edilmesi
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 boşluk ve tireleri kaldırır, on üç ASCII rakam ister, 978/979 önekini kontrol eder ve bu kitap akışı için 979-0 müzik yayını aralığını reddeder. Sonra ilk on iki haneye dönüşümlü 1 ve 3 ağırlığı uygular. Kontrol hanesi (10 − toplam mod 10) mod 10 biçiminde hesaplanır. 9780306406157 için ağırlıklı toplam 93, beklenen son hane 7'dir.
Bu doğrulama kitap metadata'sı üretmez ve kodun gerçekten tahsis edilmiş bir yayına ait olduğunu kanıtlamaz. Kontrol hanesi doğru olan ama aradığın katalogda bulunmayan bir ISBN olabilir. Eksik başlık/yazar bilgisini barkoddan tahmin etmem; gerekiyorsa ayrı bir yetkili katalog sorgusu yapar, bulunan bilgiyi kullanıcıya kontrol ettiririm.
ISBN-10 da bu küçük fonksiyonun kabul ettiği biçim değil. Elle girişte farklı standart desteklenmek isteniyorsa dönüşüm kuralı ayrıca eklenmeli. Normalize işlemi yalnız '-' ve normal boşluğu kaldırıyor; her Unicode boşluk veya rastgele biçim gürültüsünü kabul eden bir temizleyici olarak düşünülmemeli.
Aynı barkod olayı defalarca gelebilir
Kamera bir video akışıdır. Kitap aynı yerde durduğu için aynı barkod birçok karede bulunabilir. Multiple=false bir karedeki sonuç sayısını daraltır; bütün sayfa ömründe tek olay garantisi vermez. Sadece olay sonunda IsDetecting=false yapmak da arada kuyruğa girmiş callback'leri durdurmaya yetmeyebilir.
Interlocked.CompareExchange ile armed değerini 1'den 0'a yalnız bir çağrı geçirebilir. Diğer çağrılar daha UI kuyruğuna alınmadan elenir. MainThread'e geçildikten sonra active ve session yeniden kontrol edilir. Böylece eski sayfanın veya eski görünme döneminin sonucu yeni form akışını yanlışlıkla tetiklemez.
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();
}
}
Geçersiz ISBN için kullanıcıya tekrar tarama seçeneği verildiğinde, sayfa hâlâ aynı oturumdaysa armed yeniden açılır. Geçerli ISBN kabul edildiğinde ise ikinci callback'in bir kez daha formu doldurup iki kez PopAsync çağırması önlenir. Buradaki atomik kapı ile session kontrolü farklı yarışları çözüyor.
Mevcut BookAdd ViewModel'ine geri dönmek
// 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 = "";
Tarayıcıya Action<string> verilmesi, onun BookAddViewModel tipine doğrudan bağımlı olmasını önler. Çağıran sayfa kendi ViewModel örneğinin ISBN özelliğini atar; yeni bir BookAdd örneği üretmez. Böylece kullanıcının daha önce yazdığı başlık, not ve diğer alanlar korunabilir. ObservableProperty ile üretilen özellik bildirimi Entry'nin güncellenmesini sağlar.
Bu örnek NavigationPage PushAsync/PopAsync akışıdır. Shell kullanan bir uygulamada aynı çağrıları karıştırmadan Shell'in route ve dönüş sözleşmesine uygun bir taşıma seçilmeli. Callback'in ISBN'yi ataması otomatik kitap kaydı değildir; kullanıcı kalan alanları kontrol edip kayıt adımını ayrıca tamamlar.
Callback ataması başarılı ama geri gezinme başarısız olabilir. Bu durumda form alanı değişmiş olabilir; hatayı görünce otomatik ikinci bir kitap oluşturma isteği göndermem. Aynı ISBN ataması ile kalıcı kitap oluşturma ayrı yan etkilerdir. Kayıt API'sinde mükerrer ISBN politikası gerekiyorsa o kural sunucuda da uygulanır.
Sayfa kaybolduğunda hangi kaynak duruyor?
OnDisappearing session'ı geçersiz kılar, armed kapısını kapatır ve algılamayı durdurur. IsDetecting decoder davranışını kontrol eder; kamera önizlemesinin ve platform handler kaynaklarının tamamen bırakıldığını tek başına kanıtlamaz. Geri gezinme, uygulamayı arka plana alma ve yeniden açma gerçek cihazda gözlenmeli; seçilen paket sürümünün kaynak bırakma davranışı doğrulanmalıdır.
async void burada MAUI yaşam döngüsü/olay imzalarının sınırında kullanılıyor. Genel iş servislerini bu biçimde yazmam; Task döndüren metotlarla hata ve iptal sahipliğini korurum. Kamera callback'inden UI güncellemesini MainThread üzerinden yapmak da aynı sınırın parçasıdır.
Donanımdan bağımsız test ile cihaz testini ayırmak
Saf Isbn13 yardımcısını geçerli örnek, yanlış kontrol hanesi, kısa metin, harf, tireli giriş ve 979-0 ile test edebilirim. Olay akışında aynı değerin hızlı tekrarları, izin beklerken geri çıkma ve eski session cevabı denemeleri gerekir. Son cihaz turunda izin reddi, yatay/dikey yön, düşük ışık, barkodun bir kısmının görünmesi ve sayfaya tekrar giriş kontrol edilir.
Bu rehberin kamera donanımında test edildiği iddiası yok; kaynak incelemesi ve bağımsız örnek sözleşmesi anlatılıyor. Kullanıcı için tamamlanan davranış nettir: izin varsa tara, geçerli ISBN'yi mevcut forma bir kez yaz, izin veya okuma sorunu varsa elle girişe dön ve kitap kaydını kullanıcı onayıyla tamamla.
ZXing.Net.Maui kaynakları; MAUI izinleri; International ISBN Agency.