Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Offline-first React Native Apps with Expo, WatermelonDB, and Supabase

BackBlog post2023-10-08•12 minute readUsing the Supabase JavaScript Client in a React Native app, it's super easy to fetch data from a Supabase backend by simply calling functions like supabase.from('countries').select() wherever the data is needed. However, another approach to handling data in mobile apps recently gained a lot of popularity: Offline-first - meaning an app works primarily offline and synchronizes data whenever possible.This has multiple advantages, the obvious being that the app can be used offline. But even when the app is used online, the user experience still profits from this approach as reading and updating data feels instantaneous as it happens on the device first and is then synced in the background. The approach also has drawbacks: Data can become stale, and if it is changed in multiple places while offline, data conflicts can occur.There are sophisticated database technologies like CRDT (conflict-free replicated data type), which are also making their way into mobile apps. For our example, we will not reach for a CRDT and instead use WatermelonDB, an open-source solution built on SQLite that includes a sync engine and applies the simple strategy of just letting the latest change of a record win. Follow along to build a React Native/Expo app that uses WatermelonDB as the offline-first data store and syncs to a Supabase backend using Remote Procedure Calls and Supabase Realtime to trigger sync on other devices.The approach described in this blog post was developed while building Share My Stack. This mobile app allows people to curate and share their personal productivity stack as well as their favorite development stack. The app is available on the App Store, and the complete source code is available on GitHub for educational purposes. Here is a little demo of what the result looks like when using the app on two separate devices:For this tutorial, we'll be implementing a small part of the Share My Stack data model, namely the ability to have a "profile," which belongs to a "user" (stored in auth.users) and can have multiple "stacks" associated with it (see the output from the Supabase Schema Visualizer below for illustration). With that said, let's dive into building our local-first app ...The first step is to add the required dependencies to your React Native project: WatermelonDB, Supabase, and expo-build-properties, which we'll use in the next step to customize the React Native app build.WatermelonDB is depending on simdjson, which needs to be added to the Podfile of the React Native project. Using expo-build-properties, this can be done with a simple configuration change without touching native iOS project files.WatermelonDB requires models to be created for the data that is stored and synced through it. Take a look at their docs for the details; here is an excerpt of the model used in Share My Stack, which represents a "profile" that has multiple "stacks" associated with it:The WatermelonDB docs outline how to set up a sync Function that talks with the backend server. In our case, the backend is a Postgres database hosted on Supabase. As indicated earlier, we'll implement the sync via two Postgres functions called via Supabase RPC. This way, each sync is only two database calls (one push, one pull), and most of the logic is carried out directly within the database.For the local synchronize function to be able to call Postgres functions (pull and push) via supabase.rpc(), those functions have to be created via migrations before. The specific implementation of those functions will be based on the schema of the data. The push function basically receives a set of data changes as a JSON object and writes them to the database, while the pull function receives the timestamp of the last sync and compiles a JSON object containing all changes the database has accumulated since this timestamps and returns it to the client.Here is an example of a push function. This function implements the creation, deletion, and updating of profiles and would need to be extended for every additional object in the data model.And here is how the pull function might look like:To convert between timestamp formats used by WatermelonDB and Supabase/Postgres, the push and pull functions use the following utility functions in Supabase:I'm convinced local-first will be an increasingly important paradigm in the future - not for all apps, but at least for some. It can greatly enhance user experience with regards to reliability and performance and as we've seen in this tutorial, it doesn't need to be rocket science.It does, however, come with its own set of trade-offs. One of the biggest drawbacks of the demonstrated approach, in my opinion, is the amount of friction it adds to changing the data model. Usually, when just adding the Supabase SDK, I add a new migration, re-generate the types, and use the data wherever I need to.With the approach described above, I need to maintain the database schema in Supabase, and the local schema in WatermelonDB, and keep the sync functions up to date. In the end, for an app where the data model is not expected to change much and where very performant user interactions are key to adoption, this is still a very viable approach from my perspective.Next post4 October 2023On this pageRelated articlesSupabase Beta September 2023Dynamic Table Partitioning in PostgresSupabase Beta August 2023pgvector v0.5.0: Faster semantic search with HNSW indexesOrganization-based Billing, Project Transfers, Team plan



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

Offline-first React Native Apps with Expo, WatermelonDB, and Supabase

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×