Help: Barcode Scanner with Fallback to Manual Product Linking in Retool Mobile

Title: Help: Barcode Scanner with Fallback to Manual Product Linking in Retool Mobile

Hi everyone :wave:

I'm building a mobile app and need help implementing a workflow for barcode scanning and fallback selection.

What I want to do:

  • I have a barcode scanner widget that scans a value.

  • That value should be used to search the codigo_de_barras column in my productos table.

  • :white_check_mark: If there's a match:

    • Navigate to the product’s detail screen (this part is working fine).
  • :x: If there’s no match:

    • I want to show a popup with a searchable select menu.

      • The select should be typable (user can write to filter options).
      • It should list all products (nombre and the product's icon).
    • The user will manually choose the correct product.

    • Once selected, the chosen product’s codigo_de_barras should be updated with the scanned value.

    • So, the next time that barcode is scanned, it will automatically match and navigate to the product screen.

Where I’m stuck:

  • I'm not sure how to:

    • Detect when the barcode doesn’t match anything in the database.
    • Show the popup select in that case.
    • Update the codigo_de_barras field for the selected product with the scanned barcode.

Any advice on the best way to implement this logic with Retool mobile components and queries would be appreciated!

Thanks in advance :pray:
Inventario Farmasi.json (68.9 KB)

Hey @max.lopezzz

Being as there is not a pop up / modal component in Retool Mobile, I would suggest using conditional logic to show and hide the components on the itemDetailScreen.

Flow:

  1. Scan barcode
  2. Send user to itemDetailScreen
  3. Query DB or use your fitlerItems transformer using scanValue to get a result.
  4. if filterItems returns an Empty array, I would use this as an indicator to show/hide relevant components on the itemDetailScreen.
    For example if {{filterItems.value}} is empty it will be false, so you can use the hidden field on itemImage to hide it on the screen. Same for itemKeyValue.
    on this screen, I would do the inverse for a form that the user can use to look up the item and assign it the barcode in the DB using another query triggered from submitting that form.

Hope this helps point you in the right direction!

I changed my filterItems logic to the following:

// Get raw columnar data from query
const rawData = {{ getItems.data }};

// Handle case where data might be null or empty
if (!rawData || !rawData.nombre || !Array.isArray(rawData.nombre)) {
  return [];
}

// Convert columnar format to array of objects (include codigo_de_barras as int)
const data = rawData.nombre.map((_, index) => {
  return {
    id: rawData.id?.[index] ?? null,
    nombre: rawData.nombre?.[index] ?? null,
    cantidad: rawData.cantidad?.[index] ?? null,
    costo: rawData.costo?.[index] ?? null,
    precio: rawData.precio?.[index] ?? null,
    categoria: rawData.categoria?.[index] ?? null,
    subcategoria: rawData.subcategoria?.[index] ?? null,
    descripcion: rawData.descripcion?.[index] ?? null,
    foto: rawData.foto?.[index] ?? null,
    codigo_de_barras: rawData.codigo_de_barras?.[index] !== undefined && rawData.codigo_de_barras?.[index] !== null
      ? parseInt(rawData.codigo_de_barras[index], 10)
      : null
  };
});

// Filtering function (without scanValue/id filtering)
function filterItems(searchString, categoria, subcategoria, barcode) {
  let filteredData = [...data];

  // Filter by barcode if scanner1 has a value
  if (barcode !== null && barcode !== undefined && barcode !== "") {
    const barcodeInt = parseInt(barcode.toString().trim(), 10);
    if (!isNaN(barcodeInt)) {
      filteredData = filteredData.filter(item =>
        item.codigo_de_barras !== null &&
        item.codigo_de_barras === barcodeInt
      );
      // Return early since barcode is an exact match
      return filteredData;
    }
  }

  // Filter by search string in 'nombre'
  if (searchString) {
    const lowerCaseSearch = searchString.toLowerCase();
    filteredData = filteredData.filter(item =>
      item.nombre && item.nombre.toLowerCase().includes(lowerCaseSearch)
    );
  }

  // Filter by categoria
  if (categoria !== null && categoria !== undefined && categoria !== "") {
    filteredData = filteredData.filter(item =>
      item.categoria !== null &&
      item.categoria !== undefined &&
      item.categoria.toString() === categoria.toString()
    );
  }

  // Filter by subcategoria
  if (subcategoria !== null && subcategoria !== undefined && subcategoria !== "") {
    filteredData = filteredData.filter(item =>
      item.subcategoria !== null &&
      item.subcategoria !== undefined &&
      item.subcategoria.toString() === subcategoria.toString()
    );
  }

  return filteredData;
}

// Execute filtering and return result
return filterItems(
  {{ busqueda.value || "" }},
  {{ select1.value ?? null }},
  {{ select2.value ?? null }},
  {{ scanner1.value ?? null }} 
);
  1. Is this correct?
  2. I don't really understand how to use the hidden fields, could you help me out please? Thank you so much in advance :sob: