Código de barras no .NET MAUI: leitor, permissão e GTIN
Leitor de código de barras no .NET MAUI: ZXing, ML Kit, permissão de câmera, leitura repetida e GTIN de 12, 13 ou 14 dígitos validado em C# no .NET 10.
Ler código de barras num app .NET MAUI tem três partes que falham por motivos diferentes: a biblioteca que liga a câmera ao decodificador, a permissão de câmera em cada sistema e o tratamento do texto que sai da leitura. Este guia compara as duas bibliotecas mais usadas em .NET 10, mostra a alternativa do Android que nem pede câmera, e traz uma validação de GTIN em C# testada no .NET 10.0.402, que resolve o erro mais caro: o mesmo produto gravado com 12 dígitos num lugar e 13 no outro.
Qual biblioteca usar em outubro de 2026
As duas opções maduras publicam pacotes para net10.0-android36.0 e net10.0-ios26.0:
- ZXing.Net.Maui (pacote ZXing.Net.Maui.Controls, versão 0.10.4 de agosto de 2026): sucessor do ZXing.Net.Mobile, decodifica com ZXing em código gerenciado. Lê e também gera códigos. O próprio README avisa que a leitura não funciona no Windows.
- BarcodeScanning.Native.Maui (versão 3.1.0, julho de 2026): usa as APIs nativas, Google ML Kit no Android e o framework Vision da Apple no iOS, e tem suporte a Windows desde a 3.0.1. Tem modos para ler só o código na mira, útil quando a prateleira tem vários.
Na prática, para leitura contínua em balcão, inventário ou PDV, as APIs nativas costumam ler mais rápido códigos 1D pequenos e amassados, porque é o mesmo motor que a câmera do sistema usa. O ZXing faz sentido quando você também precisa gerar o código ou quer depender menos do Google Play services. Teste as duas com as suas embalagens reais antes de decidir; o emulador não reproduz foco, reflexo nem luz fraca.
Leitura avulsa no Android sem pedir a câmera
Se o app só precisa ler um código de vez em quando (um cupom, o produto para consultar o preço), o Google code scanner abre uma tela de leitura do próprio Google Play services e devolve só o resultado. Segundo a documentação, o app não precisa pedir permissão de câmera, as imagens são processadas no aparelho e a API exige Android 6 (API 23) ou superior. Para .NET existe o binding Xamarin.GooglePlayServices.Code.Scanner. É só Android e não permite personalizar a tela, então não serve para leitura contínua, mas elimina o problema da permissão negada.
Permissão de câmera: declarar, pedir na hora e ter saída
Com câmera própria, a permissão precisa existir em dois lugares. No Android, em Platforms/Android/AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
No iOS, em Platforms/iOS/Info.plist, a chave NSCameraUsageDescription com o texto que a pessoa vai ler no diálogo. Sem ela, o app é encerrado quando tenta abrir a câmera.
<key>NSCameraUsageDescription</key> <string>A câmera lê o código de barras das caixas no inventário.</string>
O pedido em si passa pela API de permissões do .NET MAUI. Duas regras evitam a tela preta:
- Peça quando a pessoa tocar em "ler", não na abertura do app. O Android recomenda pedir no contexto da ação, como descreve o guia de solicitação de permissões; no iOS, depois do primeiro "não", o diálogo não volta, e só os Ajustes liberam.
- Tenha sempre a digitação manual. Etiqueta rasgada, código em tela de outro celular e permissão negada caem no mesmo campo.
async Task<bool> PodeUsarCameraAsync()
{
var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
if (status != PermissionStatus.Granted)
status = await Permissions.RequestAsync<Permissions.Camera>();
if (status == PermissionStatus.Granted)
return true;
bool abrirAjustes = await DisplayAlert("Câmera bloqueada",
"Libere a câmera nos ajustes do app ou digite o código.",
"Abrir ajustes", "Digitar");
if (abrirAjustes)
AppInfo.Current.ShowSettingsUI();
return false;
}
O evento de leitura chega repetido e fora da thread da tela
Os decodificadores analisam quadros em sequência. Com o código parado na frente da câmera, o mesmo valor chega várias vezes por segundo, e o evento roda numa thread de fundo. Mexer na tela direto dali gera exceção; somar cada evento ao inventário multiplica o estoque. Filtre o valor repetido por uma janela curta e volte para a thread principal com MainThread:
string? ultimo;
long ultimoMs;
void AoLer(string bruto)
{
long agora = Environment.TickCount64;
if (bruto == ultimo && agora - ultimoMs < 2000) return;
ultimo = bruto; ultimoMs = agora;
MainThread.BeginInvokeOnMainThread(async () =>
{
if (!Gtin.TryNormalizar(bruto, out var gtin14))
{
Status.Text = "Leitura inválida, aproxime ou digite";
return;
}
await RegistrarAsync(gtin14);
Status.Text = $"Lido: {gtin14.TrimStart('0')}";
});
}
Environment.TickCount64 não anda para trás se o relógio do aparelho for ajustado, ao contrário de DateTime.Now. Para bipar duas caixas iguais seguidas, o operador afasta e reaproxima, ou toca em "+1"; isso é decisão de produto, e vale perguntar a quem usa.
GTIN: valide e guarde sempre com 14 dígitos
O texto lido pode ser um EAN-13 (o padrão das embalagens no varejo brasileiro), um UPC-A de 12 dígitos de produto importado, um EAN-8 de embalagem pequena ou um GTIN-14 de caixa de transporte. Os quatro são a mesma família, o GTIN, e usam o mesmo dígito verificador de módulo 10 descrito nas especificações gerais da GS1. Há um detalhe que causa duplicidade em cadastro: o ML Kit e o ZXing podem devolver o mesmo produto UPC-A como 12 dígitos, e a nota fiscal do fornecedor traz o mesmo código com um zero à esquerda, em 13. Completar com zeros à esquerda até 14 resolve: é a forma que a GS1 recomenda para guardar o GTIN.
public static class Gtin
{
// Aceita GTIN-8, GTIN-12 (UPC-A), GTIN-13 (EAN-13) e GTIN-14.
// Devolve sempre 14 dígitos, que é a chave estável para o banco.
public static bool TryNormalizar(string? lido, out string gtin14)
{
gtin14 = "";
var s = (lido ?? "").Trim();
if (s.Length is not (8 or 12 or 13 or 14) || !s.All(char.IsAsciiDigit))
return false;
s = s.PadLeft(14, '0');
if (s.All(c => c == '0'))
return false;
int soma = 0;
for (int i = 0; i < 13; i++)
{
int d = s[i] - '0';
soma += (i % 2 == 0) ? d * 3 : d; // pesos 3,1,3,1... no formato de 14 dígitos
}
int dv = (10 - soma % 10) % 10;
if (dv != s[13] - '0')
return false;
gtin14 = s;
return true;
}
}
Rodando com alguns valores num app de console no .NET 10.0.402:
7891000100103 -> válido GTIN-14 07891000100103 7891000100104 -> inválido 036000291452 -> válido GTIN-14 00036000291452 96385074 -> válido GTIN-14 00000096385074 789100010010 -> inválido 0000000000000 -> inválido ABC -> inválido
Repare no quinto caso: 12 dígitos que são um EAN-13 sem o último dígito, lidos pela metade. O verificador recusa, porque interpretado como UPC-A o último dígito não fecha a conta. É o tipo de lixo de leitura que, sem validação, vira consulta ao banco e "produto não encontrado".
Três cuidados de cadastro que vêm junto:
- Coluna de texto, nunca número. Um
BIGINTcome os zeros à esquerda e o UPC-A vira outro valor na tela. O mesmo vale para CEP e código de cliente, como explica o post sobre campo que só aceita números. - O GTIN não é a chave primária do produto. O mesmo item pode ter GTIN de unidade, de caixa e de fardo, e o fornecedor pode trocar. Guarde numa tabela de códigos ligada ao produto, como em chave primária que não é o CPF.
- Válido não quer dizer cadastrado. Se o dígito fecha e o código não existe, mostre "Produto 7891000100103 não cadastrado. Cadastrar agora?", não um erro genérico.
Teste no aparelho, com o pior caso
Use um celular de entrada, de preferência o mesmo modelo da operação; o guia de celular Android para depurar mostra como preparar. Teste com luz fraca, embalagem plástica que reflete, etiqueta curva de garrafa e código exibido na tela de outro celular. Negue a permissão de propósito e confira se o campo de digitação aparece. E se o app só precisa do número, não salve a imagem da câmera: foto do depósito é dado a mais para proteger.
Em resumo: escolha a biblioteca pelo uso (nativa para leitura contínua, ZXing se também gera códigos, Google code scanner para leitura avulsa no Android sem permissão), peça a câmera no momento do toque com saída para digitação, filtre leituras repetidas antes de tocar na tela e normalize todo GTIN para 14 dígitos validados antes de gravar.
Referência: https://github.com/JimmyPun610/BarcodeScanner.Mobile/wiki/4.-Usage-of-Maui