Salta al contenuto principale
TwentyMobile: come ho costruito un CRM mobile con Flutter, DDD e un po' di follia
  1. Coding/

TwentyMobile: come ho costruito un CRM mobile con Flutter, DDD e un po' di follia

·1175 parole·6 minuti
Andrea Luciano
Autore
Andrea Luciano
Founder of Luciosoft, specialized in native mobile development.

L’articolo che avevo promesso
#

Nell’ultimo post vi avevo raccontato perchè ho deciso di rendere TwentyMobile open source. Oggi vi porto dietro le quinte: come è costruita l’app, le scelte architetturali, e tutti quei momenti “ma perchè non funziona?” che ogni developer conosce troppo bene.

L’architettura: DDD e Connector Pattern
#

Quando ho iniziato a scrivere TwentyMobile, sapevo che non volevo un’app che funzionasse solo con Twenty CRM. L’idea era creare un client mobile che potesse, in futuro, collegarsi a qualsiasi CRM. Ambizioso? Forse. Ma l’architettura giusta fa la differenza tra un progetto che scala e uno che muore sotto il suo peso.

Ho scelto il Domain-Driven Design (DDD) combinato con un approccio Feature-First. In pratica, la struttura del progetto è così:

lib/
├── core/           # DI, Router, Theme, Notifications
├── domain/         # Modelli (Contact, Company, Note, Task, Workflow)
│   └── repositories/  # L'interfaccia astratta CRMRepository
├── data/           # TwentyConnector (il client GraphQL)
├── presentation/   # UI organizzata per feature
│   ├── onboarding/
│   ├── home/
│   ├── contacts/
│   ├── companies/
│   ├── tasks/
│   ├── workflows/
│   └── ...
└── shared/         # Widget condivisi

Il cuore di tutto è il Connector Pattern: un’interfaccia astratta CRMRepository che definisce tutti i metodi necessari (30+ metodi!) per interagire con un CRM generico. L’implementazione concreta è TwentyConnector, un client GraphQL che parla con le API di Twenty.

Se domani volessi supportare HubSpot o Salesforce? Basterebbe creare un HubSpotConnector che implementa la stessa interfaccia. La UI non cambierebbe di una virgola. Questa è la magia del DDD fatto bene.

Lo stack tecnologico
#

Vi racconto le scelte tecnologiche e perchè le ho fatte:

  • Riverpod per lo state management. Dopo anni di Provider e BLoC, Riverpod è stata una boccata d’aria fresca. Il code generation con riverpod_annotation riduce il boilerplate al minimo.

  • GoRouter per la navigazione. Supporta deep linking, parametri tipizzati, e si integra bene con Riverpod per i redirect di autenticazione.

  • Freezed per i modelli di dominio. Immutabilità garantita, copyWith automatico, e soprattutto quei factory constructor .fromTwenty() che mi permettono di mappare le risposte GraphQL di Twenty nei miei modelli di dominio.

  • GraphQL Flutter per le API. Twenty CRM espone tutto tramite GraphQL, e devo dire che una volta capito come funziona, è molto più potente di REST per un’app mobile. Chiedi esattamente i dati che ti servono, niente di più.

Le feature che mi hanno fatto impazzire
#

Il Business Card Scanner
#

Questa è stata una delle feature più divertenti da implementare. L’idea: inquadri un biglietto da visita con la fotocamera, l’app legge il testo con MLKit, e crea automaticamente il contatto nel CRM.

La realtà? I biglietti da visita sono un incubo. Font creativi, layout non standard, informazioni sparse in modo apparentemente casuale. Ho dovuto scrivere un parser personalizzato (BusinessCardParser) che cerca di capire cosa è un nome, cosa è un cognome, cosa è un’email, e cosa è un numero di telefono. Funziona nel 90% dei casi. Per quel 10% restante… beh, c’è sempre l’inserimento manuale.

Slide-to-Execute per i Workflow
#

Questa è la feature di cui sono più orgoglioso dal punto di vista UI. Quando esegui un workflow manuale di Twenty CRM dall’app, il bottone di conferma non è un semplice “Tap”. È uno slider stile iOS che devi trascinare da sinistra a destra.

Il perchè è semplice: i workflow possono fare cose importanti (inviare email, aggiornare record, triggerare webhook). Non vuoi eseguirli per sbaglio perchè hai toccato il telefono con il pollice mentre lo tiravi fuori dalla tasca.

L’implementazione include:

  • Soglia di attivazione all'85% del trascinamento
  • Feedback aptico progressivo al 25%, 50%, 75%
  • Animazione shake se qualcosa va storto
  • Anti-fat-finger threshold per evitare attivazioni accidentali

Le Note Vocali
#

In una riunione non puoi metterti a scrivere appunti sul telefono senza sembrare maleducato. Ma puoi registrare una nota vocale dopo. Ho integrato speech_to_text per la trascrizione automatica e la salvo come nota nel CRM. Veloce, pratico, e non devi nemmeno guardare lo schermo.

I problemi che nessuno ti racconta (parte 2)
#

Il TwentyConnector da 52KB — Sì, lo so. È un file enorme. Più di 50 mila byte di query GraphQL, mutation, e logica di parsing. Ogni volta che lo apro, l’IDE ci mette un secondo di troppo. Il refactoring è nella mia lista da un po’, ma come si dice… “if it ain’t broke, don’t fix it”. E funziona. Malissimo come code quality, benissimo come funzionalità.

L’autenticazione a due fattori — Implementare il flusso 2FA con OTP è stato… un’avventura. Twenty CRM supporta TOTP, e l’app deve gestire il caso in cui l’utente ha attivato la 2FA. Ho dovuto creare una schermata OTP dedicata che si inserisce nel flusso di login solo quando necessario. Il tutto con un timer che invalida il codice ogni 30 secondi. Niente di trascendentale, ma quei dettagli di UX fanno la differenza.

Il token refresh — L’app deve gestire il caso in cui l’utente mette l’app in background per ore e poi la riapre. Il token JWT sarà scaduto, e se non gestisci il refresh correttamente l’utente vede errori dappertutto. Ho implementato un AppLifecycleHandler che ri-autentica automaticamente quando l’app torna in foreground. Sembra banale, ma il numero di app che sbagliano questa cosa è impressionante.

L’AI come co-developer
#

Una cosa che mi ha sorpreso è quanto gli agenti AI abbiano contribuito al progetto. Non sto parlando di usare Copilot per l’autocomplete. Sto parlando di agenti autonomi che aprono PR, fixano bug, e ottimizzano performance.

Se guardate la history dei commit su GitHub, troverete branch come jules/optimize-country-code-search — creati automaticamente dall’agente Jules che ho integrato nel mio workflow di sviluppo. L’agente analizza le issue, scansiona il codice, propone soluzioni, e se i test passano apre una PR.

Le PR che ho ricevuto includono:

  • 🔒 Fix di sicurezza: protezione dei dati PII nei log
  • 🧹 Pulizia eccezioni swallowed nel generatore vCard
  • ⚡ Ottimizzazione del parsing degli URL avatar
  • 🔧 Aggiunta autofill hints nei form di login

Non male per un co-developer che non dorme mai e non chiede ferie.

I numeri (per i curiosi)
#

Qualche numero sul progetto:

  • 85+ commit sul branch main
  • 28+ branch di feature e fix
  • Versione 1.0.5, build 31
  • 30+ metodi nell’interfaccia CRMRepository
  • Supporto iOS e Android con layout responsivo per tablet
  • 0 dati raccolti — privacy first

Come provarlo
#

Se volete provare TwentyMobile, ecco i link:

L’app supporta due modalità di accesso: API Token per gli amministratori e Email/Password per gli utenti standard. C’è anche una Demo Mode per esplorare l’interfaccia senza configurare nulla.

Considerazioni finali
#

Costruire TwentyMobile è stata un’esperienza incredibile. Ho imparato un sacco su GraphQL, sul DDD applicato a Flutter, e su come gestire un progetto open source. Ma soprattutto ho imparato che il modo migliore per risolvere un problema è condividere la soluzione.

Se avete un’istanza Twenty CRM e vi manca un’app mobile, provatela. Se siete developer Flutter e volete contribuire, il codice è lì. E se avete domande, aprite una issue su GitHub o scrivetemi.

Alla prossima.