Docs Menu
Docs Home
/ /
Atlas Device SDKs
/ /

Sync a Realm in the Background - Java SDK

On this page

  • Prerequisites
  • Example
  • Synchronization Logic
  • Worker

If you need to sync data when your app isn't running, you can sync realms in a background process.

To get started with background synchronization, you need to add the following dependencies to your Android application:

  • androidx.work:work-runtime, to enqueue jobs

  • androidx.concurrent:concurrent-futures, to return job results from a background worker

Background sync requires two things:

  • synchronization logic

  • a scheduled job that periodically performs that logic.

First, write the custom logic that synchronizes your realm. Treat this logic as a standalone connection to your backend. As a result, you'll need to:

  • initialize the Realm SDK

  • authenticate a user to open the realm

You can use a user's cached credentials if the user recently used the app.

Open the realm, then use SyncSession.downloadAllServerChanges() and SyncSession.uploadAllLocalChanges() to synchronize the realm fully with the backend.

You can execute this logic as a background process using a subclass of ListenableWorker. Put your synchronization logic in the startWork() method of your worker:

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.work.ListenableWorker;
import androidx.work.WorkerParameters;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.TimeUnit;
import io.realm.Realm;
import io.realm.mongodb.App;
import io.realm.mongodb.AppConfiguration;
import io.realm.mongodb.Credentials;
import io.realm.mongodb.User;
import io.realm.mongodb.sync.SyncConfiguration;
public class RealmBackgroundWorker extends ListenableWorker {
static final String UNIQUE_WORK_NAME = "RealmBackgroundWorker";
private ResolvableFuture<Result> future;
public RealmBackgroundWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@SuppressLint("RestrictedApi")
@NonNull
@Override
public ListenableFuture<Result> startWork() {
future = ResolvableFuture.create();
Realm.init(this.getApplicationContext());
String appID = YOUR_APP_ID; // replace this with your App ID
App app = new App(new AppConfiguration.Builder(appID).build());
Credentials credentials = Credentials.anonymous();
app.loginAsync(credentials, it -> {
if (it.isSuccess()) {
Log.v("EXAMPLE", "Successfully authenticated.");
User user = app.currentUser();
SyncConfiguration config = new SyncConfiguration.Builder(user, "PARTITION")
.build();
Realm.getInstanceAsync(config, new Realm.Callback() {
@Override
public void onSuccess(Realm realm) {
Log.v("EXAMPLE", "Successfully opened a realm for background synchronization.");
try {
app.getSync().getSession(config).downloadAllServerChanges();
app.getSync().getSession(config).uploadAllLocalChanges();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
} else {
Log.e("EXAMPLE", "Failed login: " + it.getError().getErrorMessage());
}
});
return future;
}
}
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.concurrent.futures.ResolvableFuture
import androidx.work.ListenableWorker
import androidx.work.WorkerParameters
import com.google.common.util.concurrent.ListenableFuture
import io.realm.Realm
import io.realm.mongodb.App
import io.realm.mongodb.AppConfiguration
import io.realm.mongodb.Credentials
import io.realm.mongodb.User
import io.realm.mongodb.sync.SyncConfiguration
import java.util.concurrent.TimeUnit
class RealmBackgroundWorker(context: Context, workerParams: WorkerParameters) :
ListenableWorker(context, workerParams) {
private lateinit var future: ResolvableFuture<Result>
@SuppressLint("RestrictedApi")
override fun startWork(): ListenableFuture<Result> {
future = ResolvableFuture.create()
Realm.init(this.applicationContext)
val appID = YOUR_APP_ID // replace this with your App ID
val app = App(AppConfiguration.Builder(appID).build())
val credentials = Credentials.anonymous()
app.loginAsync(credentials) { it: App.Result<User?> ->
if (it.isSuccess) {
Log.v("EXAMPLE", "Successfully authenticated.")
val user = app.currentUser()
val config = SyncConfiguration.Builder(user, "PARTITION")
.build()
Realm.getInstanceAsync(config, object : Realm.Callback() {
override fun onSuccess(realm: Realm) {
Log.v("EXAMPLE", "Successfully opened a realm for background synchronization.")
try {
app.sync.getSession(config).downloadAllServerChanges()
app.sync.getSession(config).uploadAllLocalChanges()
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
})
} else {
Log.e("EXAMPLE", "Failed login: " + it.error.errorMessage)
}
}
return future
}
companion object {
const val UNIQUE_WORK_NAME = "RealmBackgroundWorker"
}
}

To create a worker that periodically performs background sync:

  1. Create a set of constraints that specify the conditions required for your worker.

  2. Specify how frequently your worker should execute.

  3. Enqueue your worker with the Android OS. Assign it a unique identifier so that you can update the job in the future.

You can create the background sync job inside an Application subclass in your app to guarantee that the logic only executes once every time your application runs.

Since synchronizing a realm uses data, you should consider only downloading changes in the background when the device is not:

  • low on battery

  • using a metered data source

Use Constraints to describe the environment where your background sync runs.

Your repeat interval depends on how frequently data updates in the realm and how often users open your application. If the realm frequently updates throughout the day, consider setting a repeat interval of 1-3 hours. If the realm only updates a small number of times each day, it's best to set a higher repeat interval and only background sync once or twice a day.

Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresBatteryNotLow(true)
.build();
PeriodicWorkRequest backgroundRealmSync =
new PeriodicWorkRequest
.Builder(RealmBackgroundWorker.class,
// repeat every 12 hours
12, TimeUnit.HOURS,
// execute job at any point during that 12 hour period
12, TimeUnit.HOURS)
.setConstraints(constraints)
.build();
// enqueue the work job, replacing it with the most recent version if we update it
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
RealmBackgroundWorker.UNIQUE_WORK_NAME,
ExistingPeriodicWorkPolicy.REPLACE,
backgroundRealmSync);
val constraints: Constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresBatteryNotLow(true)
.build()
val backgroundRealmSync: PeriodicWorkRequest = PeriodicWorkRequest.Builder(
RealmBackgroundWorker::class.java,
// repeat every 12 hours
12, TimeUnit.HOURS,
// execute job at any point during that 12 hour period
12, TimeUnit.HOURS
)
.setConstraints(constraints)
.build()
// enqueue the work job, replacing it with the most recent version if we update it
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
RealmBackgroundWorker.UNIQUE_WORK_NAME,
ExistingPeriodicWorkPolicy.REPLACE,
backgroundRealmSync
)

Back

Check the Network Connection