Building a Seamless Offline Experience with WatermelonDB and AdonisJs
Laying the Foundation
To get started, clone the repository and check out the v1 branch. Once you have the code and its dependencies installed, launch the app on your platform of choice using either your device or an emulator.
Syncing Data with WatermelonDB
WatermelonDB provides an out-of-the-box sync feature that allows us to keep our data in sync across devices. We’ll create a sync.ts
file inside the data
folder and define three essential functions: synchronize
, pullChanges
, and pushChanges
. These functions will enable our app to communicate with the server API and exchange data.
// sync.ts
import { Database } from '@nozbe/watermelondb';
const synchronize = async (database: Database) => {
// synchronization logic
};
const pullChanges = async (database: Database) => {
// pulling changes logic
};
const pushChanges = async (database: Database) => {
// pushing changes logic
};
Generating the AdonisJs API App
Using AdonisJs, we’ll generate a new API app by running yarn create adonis-ts-app api
. This will create a directory named api
with the necessary boilerplate code. We’ll then set up our database using MySQL and Lucid as the ORM.
yarn create adonis-ts-app api
Defining the Data Structure
To replicate the data structure from WatermelonDB, we’ll create a migration to define the weights
table in our MySQL database. We’ll also generate a Weight
model to interact with the data.
// migration.ts
import {Migration} from '@adonisjs/lucid';
export default class CreateWeightsTable extends Migration {
public async up() {
this.schema.createTable('weights', (table) => {
table.increments('id');
table.dateTime('date');
table.decimal('weight');
});
}
public async down() {
this.schema.dropTable('weights');
}
}
Building the REST API Endpoints
Next, we’ll create two REST API endpoints: one for GET requests to pull data from the server and another for POST requests to push data to the server. These endpoints will be triggered by the pullChanges
and pushChanges
functions from WatermelonDB.
// WeightController.ts
import { Controller, Get, Post } from '@adonisjs/core';
import Weight from 'App/Models/Weight';
@Controller('weights')
export default class WeightController {
@Get()
public async pullWeights() {
// pulling weights logic
}
@Post()
public async pushWeights() {
// pushing weights logic
}
}
Implementing the Sync Indicator UX
To provide a seamless user experience, we’ll create a sync indicator component that displays the syncing status to the user. This component will trigger the sync
function when the app is opened and display a success or failure message accordingly.
// SyncIndicator.js
import React, { useState, useEffect } from 'eact';
import { synchronize } from '../data/sync';
const SyncIndicator = () => {
const [syncing, setSyncing] = useState(false);
const [syncError, setSyncError] = useState(null);
useEffect(() => {
synchronize().then(() => {
setSyncing(false);
}).catch((error) => {
setSyncError(error);
setSyncing(false);
});
}, []);
return (
) : ({syncError? `Sync failed: ${syncError}` : ‘Sync successful’}
)}
);
};
export default SyncIndicator;
Testing the Offline Sync Behavior
To test our offline sync behavior, we’ll simulate an offline scenario by turning on airplane mode, adding a new weight entry, and then reconnecting to the internet. We should see the sync indicator display a success message, and the new entry should be visible in the server database.
Where to Go from Here
Congratulations! You now have a weight tracking app with full offline support. However, there’s still room for improvement. Consider adding features like:
- detailed error viewers
- data export and import capabilities
- partial sync failure handling
to take your app to the next level.