# Cómo leer un QR Code o código de barras

> Escanear QR/código de barras con la cámara usando BarcodePicker, o decodificar una Image con CameraBarcode.

Source: https://crossfly.dev/es/docs/cookbook/device/ler-codigo-de-barras — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Cómo leer un QR Code o código de barras

**Propósito.** Abrir la cámara y leer un **QR Code** o **código de barras** en vivo con el
`BarcodePicker`, o decodificar un código a partir de una `Image` ya capturada con `CameraBarcode`.

## APIs usadas
- `BarcodePicker(String title, BarcodeFormat... formats)` — ventana **abstracta** de lectura en vivo; tú la **extiendes** e implementas `onBarcode(BarcodeFormat, String)`. `formats` vacío = acepta todos.
- `abstract void onBarcode(BarcodeFormat format, String code)` — llamado por cada código nuevo decodificado que pasa el filtro de formatos.
- `BarcodePicker.formats1D` / `formats2D` (arrays estáticos) — conjuntos listos de formatos; `formats2D` contiene `QR_CODE`.
- `BarcodePicker.beep` / `BarcodePicker.vibrate` (estáticos `boolean`) — feedback al decodificar (default `true`).
- `String getAnswer()` — último código decodificado, o `null` si se canceló.
- `CameraBarcode.readBarcode(Image img)` → `com.google.zxing.Result` (o `null`) — decodifica desde una imagen estática. **NO es thread-safe.**

> **Divergencia importante.** `BarcodePicker` **es `abstract`** y usa el método `onBarcode(...)`, no un
> callback del tipo `RunnableWith`. Instancia una subclase (anónima o nombrada). El picker ya emite
> `beep()`/`vibrate(20)` al decodificar y **suprime códigos repetidos** (solo llama a `onBarcode` cuando el
> texto cambia). **No se cierra solo** al leer — llama a `hide()` dentro de `onBarcode` si quieres
> terminar en la primera lectura. Tocar la "X" (esquina inferior derecha) cancela.

## Ejemplo completo (copiar, pegar y ejecutar)

```java
package com.exemplo;

import com.google.zxing.*;                  // BarcodeFormat
import dev.crossfly.ui.*;                   // Window, Component, Button, Label
import dev.crossfly.ui.layout.*;            // VerticalLayout
import dev.crossfly.ui.picker.*;            // BarcodePicker

/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      Label resultado = new Label("");
      add(LAY_AUTO, resultado);

      Button acao = new Button("Ler QR Code");
      acao.onAction(c ->
      {
         // Lectura EN VIVO — solo QR Code; cierra en la primera lectura
         BarcodePicker picker = new BarcodePicker("Aponte para o QR", BarcodePicker.formats2D)
         {
            protected void onBarcode(BarcodeFormat format, String code)
            {
               // Llamado FUERA de la paint thread → vuelve a la UI con onUI(...)
               Component.onUI(() ->
               {
                  resultado.setText("Lido: " + code);
                  hide();            // cierra el scanner en la primera lectura (opcional)
               });
            }
         };
         picker.show();
      });
      add(LAY_AUTO, acao);
   }
}
```

> El fragmento de abajo muestra **solo el núcleo**. Para ejecutarlo, colócalo en el esqueleto `MainWindow` de arriba.

## Fragmento mínimo

```java
// verificado en la fuente: BarcodePicker.java (constructor, onBarcode, formats2D, getAnswer); CameraBarcode.java (readBarcode)
import com.google.zxing.*;                 // BarcodeFormat, Result
import dev.crossfly.ui.picker.*;           // BarcodePicker
import dev.crossfly.ui.*;                  // Component (onUI)

// 1) Lectura EN VIVO — solo QR Code; cierra en la primera lectura
BarcodePicker picker = new BarcodePicker("Aponte para o QR", BarcodePicker.formats2D) {
   protected void onBarcode(BarcodeFormat format, String code) {
      // Llamado FUERA de la paint thread → vuelve a la UI con onUI(...)
      Component.onUI(() -> {
         processar(code);   // ej.: rellenar un campo, navegar, etc.
         hide();            // cierra el scanner en la primera lectura (opcional)
      });
   }
};
picker.show();             // deja los formats vacíos en el constructor p/ aceptar todos los formatos

// 2) Decodificar de una Image estática (una cámara/thread a la vez — no es thread-safe)
Result r = CameraBarcode.readBarcode(minhaImagem);
if (r != null) {
   String texto = r.getText();
   BarcodeFormat fmt = r.getBarcodeFormat();
}
```

## Profundiza
- Guía de la cámara: [Cámara del dispositivo](/docs/data-and-services/device-camera) · Widget: [BarcodePicker](/docs/ui/widgets/barcodepicker)
- Receta hermana: [tirar-foto.md](/docs/cookbook/device/tirar-foto)
