Der Ansatz
Die App ist bewusst zweigeteilt. Alles, was Oberfläche ist – Listenverwaltung, Import, Fortschritt der Warteschlange, Einstellungen –, läuft in Flutter. Alles, was die Telefonie berührt, liegt nativ in Android. Die Naht zwischen beiden Welten sind Method Channels, und sie ist asymmetrisch gedacht: Kommandos wandern von Dart in den nativen Teil, Zustandsänderungen kommen als Ereignisstrom zurück. Das hält die Schnittstelle klein und macht sie zum einzigen Ort, an dem beide Seiten sich abstimmen müssen.
Der Zuschnitt ist kein Zufall. Der Oberflächen-Anteil einer solchen App ist der größere und der, an dem am meisten iteriert wird – dafür spielt Flutter seine Stärken aus. Der Telefonie-Anteil ist klein, aber empfindlich: Die zuständigen Android-Schnittstellen haben sich über die Versionen mehrfach verändert, etwa die Ablösung des klassischen PhoneStateListener durch TelephonyCallback ab Android 12, und die Regeln rund um Telefonie-Berechtigungen werden regelmäßig nachgeschärft. Solche Änderungen fängt man besser in einer kleinen, isolierten Kotlin-Schicht ab als verteilt über die ganze App.
Die Abarbeitung selbst ist ein Zustandsautomat auf der nativen Seite: bereit, wählt, im Gespräch, nach dem Gespräch, nächster Eintrag oder Pause. Weitergeschaltet wird nicht über Timer, sondern über die Anrufzustände, die das System meldet. Das ist der entscheidende Unterschied in der Praxis: Ein zeitgesteuerter Dialer läuft aus dem Tritt, sobald ein Gespräch länger dauert, spät angenommen wird oder in der Mailbox landet. Ein ereignisgesteuerter wartet schlicht, bis der Anrufzustand wieder auf frei wechselt.
Die Eingangsseite bleibt bewusst einfach: Nummern kommen aus den Gerätekontakten oder aus einer CSV-Datei. Die Normalisierung auf ein einheitliches Wählformat – führende Null, Ländervorwahl, Leer- und Sonderzeichen in der Schreibweise – gehört in den Import und nicht in die laufende Anrufserie. Was einmal in der Warteschlange steht, soll wählbar sein. Und weil eine Automatik, die sich nicht anhalten lässt, im Betrieb unbrauchbar ist, bleibt der aktuelle Eintrag jederzeit sichtbar und die Abarbeitung jederzeit unterbrechbar.