OdoKeep est sur l’App Store. Téléchargez-la gratuitement.

Tous les articles

Widgets iOS

Des widgets React Native sans recopier la logique métier en Swift

Une instantanée JSON versionnée permet à WidgetKit de réutiliser les calculs TypeScript, faire évoluer les échéances et protéger les données.

Mis à jour le

Dans cet article

« Prochain entretien » est un libellé court avec une longue liste de dépendances.

Dans OdoKeep, il dépend du véhicule, des entretiens enregistrés, de l'unité de distance, des reports décidés par l'utilisateur et de la prévision actuelle. Les dépenses mensuelles suivent d'autres règles, dont la conversion d'un paiement en devise étrangère à sa propre date.

Ces réponses existaient déjà en TypeScript. Ajouter des widgets iOS aurait été l'occasion de les recopier en Swift, puis de passer l'année suivante à corriger leurs désaccords.

J'ai plutôt choisi de faire écrire les réponses par l'application.

OdoKeep utilise React Native et Expo. Il possède quatre widgets WidgetKit : véhicule, échéances, dépenses mensuelles et entretien. L'extension est un processus distinct. Elle n'exécute pas React Native et n'ouvre pas le stockage MMKV de l'application.

Une instantanée JSON dans un conteneur App Group relie les deux processus. TypeScript la construit avec les modules du tableau de bord. Swift la décode et l'affiche.

Transmettre des réponses prêtes à afficher

L'instantanée est un contrat de présentation, pas une exportation des données brutes. Les montants sont formatés, les unités appliquées, les libellés traduits et les liens profonds construits. Les barres de dépenses portent même leurs libellés et leurs proportions normalisées.

Voici un extrait du contrat :

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;
}

Le fichier garde une structure utile : identifiants pour choisir un véhicule, dates pour faire évoluer l'affichage, proportions pour les barres. Swift n'a pas besoin d'une seconde implémentation des règles financières ou d'entretien.

La liste d'alertes, les prévisions d'entretien et les calculs de dépenses alimentent lib/widgets/snapshot.ts. plugins/widgets/WidgetsSnapshot.swift reflète le contrat de décodage. Quand le sens d'un champ change, la version commune change aussi. Un décodeur qui ne comprend pas cette version demande d'ouvrir l'application.

Ce repli est plus facile à diagnostiquer qu'un widget affichant avec assurance une mauvaise interprétation.

Faire avancer le calendrier sans ouvrir l'application

Le calendrier constitue l'exception volontaire au précalcul. Un widget peut rester visible plusieurs jours sans lancement de l'application. « Dans trois jours » doit évoluer à minuit sans demander à React Native de reconstruire le fichier.

L'instantanée stocke donc un jour d'échéance local, un jour warningFrom et des textes traduits indexés par décalage :

"0"  -> Aujourd'hui
"1"  -> Demain
"2"  -> Dans 2 jours
"-2" -> En retard de 2 jours

La vraie table provient de la fonction de traduction et couvre soixante jours passés et 120 futurs, avec un texte de repli pour les retards plus anciens. Swift compte les jours calendaires et récupère le texte prêt à afficher. Il peut activer un avertissement à la date prévue sans connaître le calcul ayant choisi cette date.

Pour l'entretien mesuré en distance, l'instantanée contient un libellé fixe. Une nuit qui passe n'ajoute pas de kilomètres au compteur.

WidgetTimeline.swift crée une entrée immédiate et une par minuit local pendant une semaine, avec la politique .atEnd. Le fichier ne change pas ; la date de l'entrée change. Cela fournit des états futurs à WidgetKit sans garantir une exécution à une heure exacte. WidgetKit contrôle sa planification, comme l'explique la documentation Apple sur l'actualisation des widgets.

Remplacer le fichier avant de demander le rechargement

L'écriture principale intervient quand l'application quitte le premier plan, près du retour à l'écran d'accueil. Un temporisateur de quatre secondes après le montage fournit une première instantanée si la session n'est pas encore passée en arrière-plan. Les changements observables de préférences et de configuration réarment ce temporisateur.

Le dépôt n'expose pas d'abonnement à chaque écriture d'enregistrement. Une entrée ajoutée après le temporisateur n'atteint donc le widget qu'au prochain passage en arrière-plan. Voilà la limite de fraîcheur. Le widget ne peut pas non plus connaître une modification partagée que l'application n'a pas encore récupérée.

Le fichier est écrit de façon synchrone lors de cette transition. Le JSON complet reçoit d'abord un nom temporaire, puis remplace l'instantanée réelle par déplacement avec écrasement. Le lecteur habituel évite ainsi un document à moitié écrit. Un fichier temporaire laissé par une interruption est supprimé avant réutilisation.

Ensuite seulement, un petit module Expo natif demande à WidgetKit de recharger les chronologies. Écrire des octets ne suffit pas à remplacer celle en cours. La demande reste soumise au planificateur iOS et au succès de l'écriture.

Appliquer la confidentialité dans le constructeur

Les données transmises sont plus restreintes que celles disponibles dans l'application. Avec le verrou biométrique, l'instantanée ne contient ni kilométrage ni montants. Ces champs sont nuls ; les valeurs sensibles ne sont pas sérialisées avant d'être cachées. La plaque d'immatriculation n'est jamais incluse. Les échéances et informations d'entretien restent disponibles.

Les vues Swift marquent aussi certaines valeurs comme sensibles pour permettre le masquage système. Cela complète les contrôles d'affichage iOS ; le constructeur décide, lui, quelles valeurs arrivent dans l'extension.

Déconnexion et suppression du compte effacent l'instantanée et demandent un rechargement. Cette suppression appartient à la fin de session, pas uniquement au démontage du hook. Sinon, le fichier pourrait survivre au compte qui l'a produit.

Réutiliser le principe avec Siri et Raccourcis

Les intents de lecture de Siri et Raccourcis utilisent une autre instantanée dans le répertoire Documents de l'application. Ils sont compilés dans la cible principale, pas dans l'extension WidgetKit. Les constructeurs partagent leurs entrées, mais fichiers et consommateurs ont des contrats distincts.

Un raccourci d'écriture ouvre un lien profond vers un formulaire existant. Il ne modifie pas le stockage depuis Swift. La route applique toujours le rôle de partage, les limites de l'offre, les contrôles du compteur et la synchronisation. Les paramètres Siri préremplissent une demande sans contourner le parcours d'écriture.

Garder une extension reproductible avec Expo prebuild

Le projet régénère son dossier iOS ignoré par Git avec Expo prebuild. Une cible ajoutée à la main dans Xcode disparaîtrait. plugins/with-widgets.js crée donc l'extension, relie fichiers Swift et ressources, déclare App Group et décrit la seconde cible à EAS pour la signature. C'est le modèle des config plugins Expo pour les changements devant survivre à une régénération native.

Un réglage mérite une explication : ENABLE_DEBUG_DYLIB = NO pour l'extension. L'implémentation documente un cas où l'extraction des métadonnées App Intents inspectait les dépendances du lanceur de débogage, ne trouvait pas AppIntents et ignorait l'extraction sans faire échouer le build. Les widgets configurables n'avaient alors aucun intent utilisable et restaient vides malgré une session ouverte. Ce réglage donne à l'extension la disposition binaire attendue.

Les tests vérifient le constructeur pur, les champs requis par Swift, les liens, les traductions, couleurs, polices et paramètres natifs. Ce sont des contrôles ciblés du contrat, pas un schéma multilangage généré ni un remplacement des essais sur appareil. Ils détectent des divergences entre consommateurs compilés séparément avant qu'elles arrivent sur l'écran d'accueil.

Je réutiliserais cette frontière dans un autre produit React Native : l'application possède le sens de ses données et transmet une description bornée et versionnée de l'affichage. Les dates gardent assez de structure pour évoluer ; la fraîcheur des autres réponses dépend explicitement de la dernière écriture.

Ces quatre widgets existent dans OdoKeep pour iOS. Ils permettent de consulter échéances et entretien sans ouvrir le carnet complet.