site-icon Hong Cheung
Jun 8, 2025

Enfin, la synchronisation hors ligne fonctionne pour Appwrite et RxDB

Introduction

Ce n’était pas difficile, mais je ne dirais pas que c’est du plug-and-play. Il m’a fallu quelques semaines pour que les fondations tournent correctement, la principale difficulté étant le manque d’exemples et de tutoriels. Comparé à Powersync + Supabase ou à l’utilisation de Firebase, cela demande clairement un peu de configuration.

Implémentation

Il existait un article de blog officiel avec un exemple git sur la façon de faire fonctionner Appwrite + RxDB. Cependant, quand je l’ai essayé, il était plutôt bogué et il ne faisait pas de synchronisation en temps réel. J’ai donc fini par construire le mien de zéro en lisant les documentations d’Appwrite et de RxDB.

Principaux obstacles

Gestion de l’état avec RxDB

J’utilise LegendState, et il m’arrive d’essayer de faire un .set() sur l’instance de ma collection rxdb parce que cela provoque une boucle dans ma vue. Je dois donc m’assurer d’utiliser .get() pour la collection rxdb, et d’utiliser LegendState pour lire et surveiller les mises à jour. D’un autre côté, dans mon viewModel, j’ai toutes les fonctions qui manipulent directement rxdb. La modification de la réplique Appwrite se fait dans les fonctions du viewModel.

La synchronisation en temps réel met à jour la vue en boucle

En lien avec le point précédent. Je ne connais pas bien LegendState, mais je m’améliore maintenant. J’ai ajouté un debounce pour résoudre le problème.

Le délai de synchronisation perturbe l’état local

Comme la réplication RxDB vers Appwrite ne peut effectuer qu’une seule écriture à la fois, chaque fois que j’effectuais plusieurs actions en peu de temps, les états locaux entraient en conflit. C’est parce que j’ai abonné la collection en temps réel, alors que les données modifiées localement et celles reçues ne sont pas synchronisées. J’ai donc créé un champ updatedAt pour gérer les conflits.

Champs supprimés

Vous pouvez définir un champ deleted pour la réplication. Il est indiqué d’effectuer la suppression avec .remove(). Cependant, cette méthode de suppression ne permet pas d’« annuler la suppression ». C’est parce que RxDB masque tout ce qui est supprimé logiquement via sa méthode .remove(), le client ne peut pas accéder à l’élément simplement en vérifiant !deleted. Il existe une méthode get/findone pour récupérer un élément supprimé, ce qui est plutôt pénible, mais je comprends que rxdb ait besoin de son propre mécanisme de suppression logique. J’ai donc ajouté un champ deleted supplémentaire pour moi. Au total, il y a donc 3 champs deleted : rxDeleted, isDeleted et deleted (par défaut dans Appwrite).

Maintenant, quand un utilisateur supprime un élément, je ne bascule que isDeleted, afin qu’il puisse aller dans la corbeille pour être récupéré. Côté client, je mets en place une vérification de 30 jours sur isDeleted pour effectuer la suppression logique de rxdb qui bascule rxDeleted. Ensuite, côté serveur, j’effectue une autre vérification, disons 30 jours de rxDeleted, pour les supprimer réellement.

La suppression logique via rxDeleted est nécessaire pour que RxDB synchronise le champ deleted entre les appareils. Mais je veux aussi que l’utilisateur puisse facilement récupérer l’élément, donc j’ai ajouté isDeleted par-dessus.

Conclusion

Ce n’était pas si facile pour moi, il m’a fallu des semaines pour résoudre tout cela, car j’ai commencé avec SwiftUI où Apple CoreData gérait toutes les procédures de synchronisation pour moi. C’est la première fois que je dois réellement en faire plus pour simplement synchroniser. Mais je suis content de l’avoir fait.


Maintenant, je peux passer à la prochaine grande étape !

Précédent