OdoKeep è ora su App Store. Scaricala gratis.

Tutti gli articoli

Widget iOS

Widget React Native senza duplicare la logica di business in Swift

Un’istantanea JSON versionata permette a WidgetKit di riusare i calcoli TypeScript, aggiornare scadenze e rispettare la privacy.

Aggiornato il

In questo articolo

«Prossimo intervento» è un'etichetta breve con molte dipendenze.

In OdoKeep può dipendere da veicolo, manutenzione registrata, unità di distanza, rinvii dell'utente e previsione attuale. La spesa mensile segue altre regole, compresa la conversione di una voce in valuta estera alla data del pagamento.

Le risposte esistevano già in TypeScript. Aggiungere widget iOS sarebbe stata una buona occasione per copiarle in Swift e passare l'anno seguente a correggere divergenze.

Ho preferito far scrivere le risposte all'app.

OdoKeep usa React Native ed Expo e ha quattro widget WidgetKit: veicolo, scadenze, spesa mensile e manutenzione. L'estensione è un processo separato. Non esegue React Native e non apre lo storage MMKV dell'app.

Il confine è un'istantanea JSON in un contenitore App Group. TypeScript la costruisce con gli stessi moduli della dashboard. Swift la decodifica e la mostra.

Condividere risposte pronte tra processi

L'istantanea è un contratto di presentazione, non un'esportazione di record grezzi. Importi e unità sono formattati, etichette tradotte e deep link già costruiti. Anche i gruppi del grafico di spesa contengono etichette e proporzioni normalizzate delle barre.

Ecco un estratto del contratto:

interface WidgetSpendBucket {
  id: string;
  label: string;
  ratio: number;
  isCurrent: boolean;
}

interface WidgetDeadline {
  id: string;
  label: string;
  vehicleId: string;
  vehicleName: string;
  dueDate: string;
  dueDateLabel: string;
  warningFrom: string;
  url: string;
}

Resta una struttura utile. Swift necessita degli identificatori per scegliere il veicolo, delle date per far avanzare la vista e delle proporzioni per disegnare le barre. Non gli serve un'altra implementazione delle regole economiche o di manutenzione.

Avvisi della dashboard, previsioni e calcoli alimentano lib/widgets/snapshot.ts. plugins/widgets/WidgetsSnapshot.swift rispecchia il contratto del decoder. Se cambia il significato di un campo, cambia anche la versione comune. Un decoder che non capisce quella versione invita ad aprire l'app.

È un errore molto più facile da indagare di un widget che presenta con sicurezza un'interpretazione sbagliata.

Far avanzare il calendario senza l'app

Il calendario è l'eccezione intenzionale al precalcolo. Un widget può restare visibile per giorni senza aprire l'app. «Tra tre giorni» deve cambiare dopo mezzanotte senza chiedere a React Native di ricostruirlo.

L'istantanea salva quindi il giorno locale della scadenza, il giorno warningFrom e stringhe tradotte indicizzate per distanza in giorni:

"0"  -> Oggi
"1"  -> Domani
"2"  -> Tra 2 giorni
"-2" -> Scaduto da 2 giorni

La tabella reale usa la funzione di traduzione, copre sessanta giorni passati e 120 futuri e include un testo di ripiego per ritardi più vecchi. Swift conta giorni di calendario e cerca il testo già pronto. Può attivare l'avviso alla data prestabilita senza conoscere il calcolo che l'ha scelta.

Per interventi basati sulla distanza viene salvata un'etichetta fissa. Una notte trascorsa non aggiunge chilometri al contachilometri.

WidgetTimeline.swift crea una voce per ora e una per ogni mezzanotte locale della settimana successiva, con politica .atEnd. L'istantanea resta uguale, cambia la data della voce. Questo fornisce stati futuri a WidgetKit senza promettere un'esecuzione a un istante esatto. La pianificazione dipende dal sistema, come spiega la documentazione Apple sull'aggiornamento dei widget.

Sostituire il file prima di chiedere il ricaricamento

La scrittura principale avviene quando l'app lascia il primo piano, vicino al ritorno alla schermata Home. Un timer di quattro secondi dopo il montaggio produce una prima istantanea se la sessione non è ancora passata in background. Cambiamenti osservabili di preferenze e configurazione riattivano quel timer.

Il repository non espone una sottoscrizione a ogni scrittura. Una voce aggiunta dopo lo scatto del timer entra quindi nell'istantanea alla successiva transizione in background. Questo è il limite di freschezza. Il widget non può conoscere una modifica condivisa che l'app non ha ancora scaricato.

Lo scrittore usa operazioni sincrone in quel passaggio. Scrive tutto il JSON con un nome temporaneo, poi lo sposta sopra il file reale con sovrascrittura. Il decoder evita così il normale accesso a un documento parziale. Un temporaneo lasciato da un'interruzione viene eliminato prima del riutilizzo.

Solo dopo la sostituzione, il piccolo modulo Expo nativo chiede a WidgetKit di ricaricare le timeline. Scrivere byte non cambia quella esistente finché il sistema non ne richiede un'altra. Il reload resta una richiesta al pianificatore e dipende da iOS e dal successo della scrittura.

Applicare la privacy nel costruttore

I dati oltre il confine sono meno di quelli disponibili nell'app. Con il blocco biometrico attivo, l'istantanea non contiene valori di contachilometri o denaro. I campi sono nulli: i dati sensibili non vengono serializzati per poi nasconderli. La targa non viene mai inclusa, qualunque sia il blocco. Scadenze e manutenzione rimangono disponibili.

Le viste Swift marcano anche i valori sensibili per l'oscuramento del sistema. Questo supporta i controlli di iOS; il costruttore decide quali dati raggiungono proprio l'estensione.

Disconnessione ed eliminazione dell'account rimuovono il file e chiedono un reload. La rimozione fa parte della chiusura di sessione, non solo dello smontaggio dell'hook. Altrimenti l'istantanea potrebbe sopravvivere all'account che l'ha prodotta.

Riutilizzare il confine con Siri e Comandi Rapidi

Gli intent di lettura di Siri e Comandi Rapidi usano un'istantanea distinta nella directory Documents dell'app. Compilano nel target principale, non nell'estensione WidgetKit. I costruttori condividono gli input, ma file e consumatori hanno contratti distinti.

Un comando di scrittura apre un deep link verso un modulo esistente. Non aggiorna lo storage da Swift. Il percorso applica ancora ruolo di condivisione, limiti del piano, controlli sul contachilometri e sincronizzazione. I parametri Siri precompilano una richiesta senza aggirare il percorso di scrittura.

Riprodurre l'estensione con Expo prebuild

Il progetto rigenera la directory iOS ignorata da Git tramite Expo prebuild. Un target aggiunto a mano in Xcode non sopravviverebbe. plugins/with-widgets.js crea quindi l'estensione, collega file Swift e risorse, dichiara App Group e descrive a EAS il secondo target per la firma. Segue il modello dei config plugin Expo per modifiche che devono sopravvivere alla rigenerazione nativa.

Un'opzione merita attenzione: ENABLE_DEBUG_DYLIB = NO per l'estensione. L'implementazione documenta un caso in cui l'estrazione dei metadati App Intents ispezionava le dipendenze dell'eseguibile ausiliario di debug, non trovava AppIntents e saltava l'estrazione senza far fallire la build. I widget configurabili rimanevano senza intent utilizzabile e mostravano uno stato vuoto nonostante la sessione aperta. L'opzione fornisce la disposizione binaria attesa da quel percorso.

I test controllano costruttore puro, campi richiesti dal decoder Swift, deep link, traduzioni, colori, font e configurazione nativa. Sono verifiche mirate del contratto, non uno schema generato tra linguaggi o un sostituto delle prove sul dispositivo. Rilevano divergenze tra consumatori compilati separatamente prima che arrivino alla Home.

Riutilizzerei questa separazione in un altro prodotto React Native: l'app possiede il significato dei dati e invia una descrizione limitata e versionata di ciò che si può mostrare. Le date mantengono struttura sufficiente per avanzare; l'età delle altre risposte deriva esplicitamente dall'ultima scrittura.

I quattro widget sono disponibili in OdoKeep per iOS. Permettono di controllare scadenze e prossimo intervento senza aprire il diario completo.