Traverba: Een Flutter-app met 3 ASR-modellen, een lokale LLM en camera-OCR — volledig op het apparaat

Ik heb Traverba gebouwd — een realtime vertaler die 100% op je telefoon draait, zonder cloud-API's. Spraakherkenning, vertaling naar 108 talen, camera-OCR in 92 schriftsystemen, schermvertaling als zwevende overlay, bestandstranscriptie en offline groepschat via Bluetooth voor maximaal 7 personen.

De hele app is gebouwd met Flutter aangevuld met native platformcode voor het zware werk. Hier leg ik uit wat de app doet, waarom Flutter de juiste keuze was, en welke uitdagingen ik tegenkwam bij het uitbrengen op zowel iOS als Android.

Wat de App Doet

Traverba heeft vijf kernfuncties, allemaal offline beschikbaar:

Live Spraakverta­ling — Spreek in de ene taal, hoor en lees de vertaling in de andere. Drie modi: in-app, zwevende systeemaudiocapture (vertaalt audio van Zoom/Teams/Meet) en zwevende microfoon. Automatische voorlezing in de doeltaal.

Cameravertaling — Richt je camera op een menu, bord, formulier of document. Twee OCR-engines: Basic modus (ML Kit) voor snelle scans, Professional modus (PaddleOCR) voor complexe schriften zoals CJK, Arabisch, Cyrillisch en Devanagari. Ondersteunt 92 talen. De app vervangt buitenlandse tekst direct in de afbeelding door de vertaling.

Schermvertaling — Een zwevende overlay die tekst in elke app vertaalt. Anime kijken, Koreaans nieuws lezen, een Japans spel spelen of een WeChat-bericht ontvangen? Tik op de overlayknop en de tekst wordt ter plekke vertaald. Geen app-wisseling nodig.

Bestandstranscriptie — Importeer audio- of videobestanden (mp3, m4a, wav, mp4) en ontvang getimestampte, naast-elkaar-geplaatste transcripten in bron- en doeltaal.

Offline Groepschat — Maximaal 7 personen verbinden via Bluetooth mesh. Iedereen spreekt zijn eigen taal. Elk bericht wordt in realtime vertaald naar de taal van elke deelnemer. Geen internet. Geen server. Elke telefoon verwerkt zijn eigen ASR en vertaling zelfstandig.

Dit alles draait lokaal op de processor van de telefoon. Geen API-sleutels, geen serverkosten, geen data die het apparaat verlaat.

Waarom Flutter

Aan het begin evalueerde ik Flutter, React Native en volledig native (aparte Swift- en Kotlin-codebases).

Een enkele codebase voor meerdere platforms was niet onderhandelbaar. Als solodev twee native codebases onderhouden voor een app van deze complexiteit was simpelweg niet realistisch. De Dart-UI-laag — instellingen, chatbubbels, transcriptweergaven, onboarding-tour, downloadsheets, taalkiezers — beslaat ongeveer 60% van de totale code. Dit tweemaal schrijven had de projectduur verdubbeld.

Flutter's platform channel-systeem maakte native integratie praktisch haalbaar. Het AI-zware werk (ASR-inferentie, LLM-vertaling, OCR, Bluetooth mesh, zwevende overlays) draait in native Swift/Kotlin via platform channels en method channels. Flutter is niet de AI-runtime — het is de UI- en orkestratielaag die de native engines met elkaar verbindt.

De prestaties waren goed genoeg. De zorg bij Flutter voor dit soort apps is overhead — elke milliseconde telt als je ASR → vertaling → TTS aaneenrijgt in een gesprek. In de praktijk voegt de Flutter-laag verwaarloosbare latency toe, omdat de zware berekeningen in native code plaatsvinden. De Dart-laag beheert toestandsbeheer, routering en UI-updates — en doet dat goed.

Hot reload versnelde UI-iteratie enorm. Met vijf afzonderlijke feature-oppervlakken (spraak, camera, scherm, bestand, groepschat), elk met meerdere toestanden en modi, was de mogelijkheid om de UI te itereren zonder herbouw een aanzienlijke productiviteitsmultiplier.

De Architectuur: Flutter als Orkestrator

De app volgt een duidelijke scheiding:

Flutter/Dart-laag beheert:

  • Alle UI (Material 3, adaptieve layouts voor telefoon en tablet)
  • Toestandsbeheer en feature-routering
  • Taal- en locatieselectie en gebruikersvoorkeuren
  • Downloadbeheer voor optionele modellen
  • Munteenheideconomie en abonnementsgating
  • Onboarding-rondleiding

Native laag beheert (via platform channels):

  • ASR-inferentie (sherpa-onnx / whisper.cpp)
  • LLM-vertaling (Gemma via on-device runtime)
  • Traditionele ML-vertaling (ONNX NMT-modellen)
  • Camera-OCR (ML Kit + PaddleOCR)
  • Text-to-speech
  • Bluetooth mesh voor groepschat
  • Zwevende overlay (schermvertaling + systeemaudiocapture)
  • Geheugenbudgetbeheer en modelladen/uitwijzen

De brug tussen deze lagen is een set platform channels met goed gedefinieerde contracten. De Dart-kant stuurt opdrachten ("start ASR voor Kantonees," "vertaal deze tekst van Japans naar Engels," "neem scherm op en voer OCR uit"). De native kant geeft resultaten asynchroon terug.

Drie ASR-modellen, Automatische Routering

Geen enkel spraakherkenningsmodel dekt alle talen goed. In plaats van slechte kwaliteit voor sommige talen te accepteren, levert de app drie gespecialiseerde modellen:

  • Parakeet Geoptimaliseerd voor Engels. Meegeleverd met de app voor directe Engelse ASR zonder download.
  • Whisper.cpp Brede meertalige dekking voor 30+ talen.
  • Qwen3-ASR Geoptimaliseerd voor CJK met speciale ondersteuning voor Kantonees.

Wanneer de gebruiker een brontaal selecteert, kiest de Dart-routeringslaag het beste model en vertelt de native kant welke engine te activeren. De gebruiker ziet de modelselectie nooit en hoeft er niets aan te beheren.

Het lastige gedeelte is geheugen. Deze modellen variëren van 500 MB tot 1,9 GB RAM. Alle drie tegelijk laden zou de meeste telefoons laten crashen. Een geheugenbudgetplanner (geschreven in Dart, die native geheugenstatistieken opvraagt) houdt bij wat geladen is en verwijdert het minst recent gebruikte model wanneer een nieuw model moet laden. Een backend-voorkeurarchief onthoudt welke compute-backend (NPU, GPU, CPU) op elk specifiek apparaat werkte, zodat volgende runs mislukte backends overslaan.

108 Talen via Twee Vertaalniveaus

Vertaling verloopt via twee paden:

Niveau 1 — Traditionele NMT-modellen (61 talen): Snel, laag geheugengebruik, goede kwaliteit voor goed ondersteunde taalparen. ONNX-modellen draaien via sherpa-onnx op beide platforms.

Niveau 2 — On-device Gemma LLM (47 extra talen): Voor talen zonder speciale NMT-modellen (Yoruba, Amhaars, Lao, Birmaans en anderen) verwerkt een gekwantiseerd Gemma 2B-model de vertaling lokaal. Langzamer dan Niveau 1, maar het breidt de dekking uit naar talen die traditionele modellen slecht bedienen.

De Dart-laag beheert welk niveau gebruikt wordt op basis van het taalpaar. De gebruiker ziet een uniforme ervaring.

Camera-OCR: Dual Engine, 92 Talen

Cameravertaling draait twee engines die de gebruiker kan selecteren:

Basic modus gebruikt Google ML Kit's on-device tekstherkenning. Snel, goed voor Latijns, Cyrillisch en CJK. Geschikt voor realtime zoekervaring.

Professional modus gebruikt PaddleOCR on-device. Betere nauwkeurigheid voor complexe lay-outs, gemengde schriften, gebogen tekst en laag contrast. Gebruikt een muntgate (5 munten per opname) omdat het rekenintensief is.

Een les die ik op de harde manier leerde: iOS bundelt standaard alleen het Latijnse OCR-scriptmodel. CJK, Devanagari en Arabisch vereisen dat je scriptspecifieke pods expliciet toevoegt aan de Podfile. De fout is volledig stil — ML Kit geeft lege resultaten terug voor niet-ondersteunde schriften zonder enige foutmelding. Als je meertalige OCR bouwt op iOS met ML Kit, controleer dan je Podfile.

Zwevende Overlay: Native, Niet Flutter

De schermvertaling en systeemaudiofuncties draaien als native overlays buiten Flutter's renderingoppervlak. Op Android is dit een systeem-overlayservice geschreven in Kotlin. Op iOS gebruikt het schermopname-API's in Swift.

Deze overlays communiceren terug met de Flutter-vertaalengine via platform channels. De architectuuruitdaging is levenscyclusbeheer — de overlay moet actief blijven terwijl de hoofd-Flutter-activiteit mogelijk op de achtergrond staat.

Een cruciale les: op iOS moeten native bridge-instanties sterk worden vastgehouden (statisch of bewaard door een singleton). Als de brug een instantievariabele is die niets vasthoudt, zal ARC hem garbage-collecten en worden alle handler-closures stilletjes no-ops. Ik verloor dagen aan een downloadvoortgangsindicator die op 0% bleef steken omdat de brug werd verzameld. Geen crash, geen fout, gewoon stille storing.

Bluetooth Groepschat

De offline groepschat gebruikt een Bluetooth mesh-protocol. Elke telefoon fungeert als zowel ontvanger als doorstuurpunt. Berichten worden uitgezonden naar alle verbonden apparaten. Elk apparaat voert zijn eigen ASR en vertaling onafhankelijk uit — er is geen "host"- of "server"-telefoon.

Flutter beheert de chat-UI, berichttoestand en deelnemersbeheer. De Bluetooth-stack is volledig native (CoreBluetooth op iOS, Android Bluetooth API) overbrugd via platform channels.

De totale pipeline-latency (spraak → ASR → vertalen → uitzenden → weergeven) is ongeveer 1 seconde. De Dart-UI gebruikt optimistische weergave (toont direct "vertalen..." bij verzending) zodat het gesprek responsief aanvoelt.

Cijfers

  • Talen: 108 totaal (61 ML + 47 AI)
  • ASR-talen: 30 on-device spraakherkenning
  • OCR-talen: 92 cameratekstherkenning
  • Basis app-grootte: ~150 MB + downloadbare modellen
  • Platformen: iOS + Android vanuit één Flutter-codebase
  • Dart/Flutter-code: ~60% van de totale codebase (UI, toestand, routering, bedrijfslogica)
  • Native code: ~40% (ASR, LLM, OCR, Bluetooth, overlays, geheugenbeheer)
  • Teamgrootte: 1

Wat Flutter Goed Deed voor Dit Project

Enkele codebase voor complexe UI. Vijf feature-oppervlakken, elk met meerdere modi, downloadsheets, onboarding-rondleiding, adaptieve layouts — dit eenmalig schrijven in plaats van tweemaal was het verschil tussen wel of niet uitleveren als solodev.

Platform channels zijn goed ontworpen. De brug tussen Dart en native code is schoon, goed gedocumenteerd en betrouwbaar. Voor een app waarbij de zware berekeningen native zijn maar de gebruikerservaring Flutter is, is dit precies de juiste architectuur.

Hot reload voor snelle iteratie. Layouts verfijnen, toestanden testen, UX-stromen itereren — hot reload reduceerde iteratietijd van minuten naar seconden over honderden UI-wijzigingen.

Het ecosysteem is volwassen. Toestandsbeheer (Riverpod), navigatie, lokalisatie (117 locales), adaptief design — het Flutter-ecosysteem heeft oplossingen van productiekwaliteit voor dit alles.

Wat Lastig Was

Geheugendruk is Flutter's blinde vlek. Flutter biedt geen fijnmazige geheugencontrole. Wanneer je meerdere native ML-modellen tegelijk laadt naast de Flutter-engine, moet je geheugen op native niveau beheren en dit terugkoppelen naar Dart. Er is geen ingebouwde Flutter-API voor "hoeveel RAM gebruikt mijn app" of "wat is de systeemgeheugendruk."

Native overlay-levenscyclus. Zwevende overlays die buiten het Flutter-oppervlak werken, zijn architectureel complex. De Flutter-activiteit kan op de achtergrond staan terwijl de overlay actief is. De brug levend houden, toestandssynchronisatie beheren en platformspecifieke levenscyclusgebeurtenissen afhandelen vereiste aanzienlijke native code die Flutter niet kan abstraheren.

iOS-buildcomplexiteit. CocoaPods met ML Kit-scriptpods, entitlements (increased-memory-limit voor het gelijktijdig laden van ASR + LLM), App Tracking Transparency-timing, code signing — de iOS-buildpipeline heeft veel bewegende onderdelen die Flutter's tooling niet volledig beheert.

Probeer Het

Traverba is gratis op beide platforms. Spraak-, camera-, schermvertaling en tekstvertaling zijn allemaal gratis en onbeperkt. Premium ($1.99/maand) ontgrendelt groepschat, bestandstranscriptie en vergaderingsamenvattingen.

Beschikbaar op Google Play en App Store. Meer informatie op traverba.com.

Ik beantwoord graag vragen over de architectuur, Flutter + native integratiepatronen of on-device AI in het algemeen.