Navigation

    Quick Start with Sync - Android SDK

    This page contains information to quickly get Realm Database integrated into your app. Before you begin, ensure you have:

    Info With Circle IconCreated with Sketch.Note
    Check Out the Tutorial

    This page contains only the essential information that you need to set up a MongoDB Realm application. If you prefer to follow a guided tutorial that shows you step-by-step how to set up a working app, check out the Android Tutorial where you'll build a mobile app that connects to the Task Tracker backend.

    Before you can use Realm in your app, you must initialize the Realm library. Your application should initialize Realm just once each time the application runs.

    To initialize the Realm library, provide an Android context to the Realm.init() static function. You can provide an Activity, Fragment, or Application context for initialization with no difference in behavior. You can initialize the Realm library in the onCreate() method of an application subclass to ensure that you only initialize Realm once each time the application runs.

    Realm.init(this) // context, usually an Activity or Application
    Info With Circle IconCreated with Sketch.Note
    Register Your Application Subclass in the Android Manifest

    If you create your own Application subclass, you must add it to your application's AndroidManifest.xml to execute your custom application code. Set the android.name property of your manifest's application definition to ensure that Android instantiates your Application subclass before any other class when a user launches your application.

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mongodb.example">
    <application android:name=".MyApplicationSubclass" ... />
    </manifest>

    To use MongoDB Realm features such as authentication and sync, you must access your Realm app using your Realm app ID. You can find your Realm app ID in the Realm UI.

    val appID : String = YOUR_APP_ID;
    app = App(AppConfiguration.Builder(appID)
    .build())
    Info With Circle IconCreated with Sketch.Note
    Android Studio Errors?

    If Android Studio does not recognize the Realm, App, or AppConfiguration types, there could be a problem with the your Gradle build configuration. To fix the issue:

    • Clean your project with Build > Clean Project
    • Rebuild your project based on your updated build.gradle file with Build > Rebuild Project
    • Revisit the Install the Android SDK guide to make sure that you installed the dependencies correctly.

    Your application's data model defines the structure of data stored within Realm Database and synchronized to and from MongoDB Realm. You can define your application's data model in two ways:

    This quick start uses the latter approach, which defines your schema using classes in your mobile application code. To define your Realm app's object model in this way, you need to enable Developer Mode.

    Once you've enabled Developer Mode, add the following class definitions to your application code:

    Bulb IconTip
    See also:

    See Configure Your Data Model for more information on Realm Schema, object models, and Developer Mode.

    enum class TaskStatus(val displayName: String) {
    Open("Open"),
    InProgress("In Progress"),
    Complete("Complete"),
    }
    open class Task(_name: String = "Task", project: String = "My Project") : RealmObject() {
    @PrimaryKey
    var _id: ObjectId = ObjectId()
    var name: String = _name
    @Required
    var status: String = TaskStatus.Open.name
    var statusEnum: TaskStatus
    get() {
    // because status is actually a String and another client could assign an invalid value,
    // default the status to "Open" if the status is unreadable
    return try {
    TaskStatus.valueOf(status)
    } catch (e: IllegalArgumentException) {
    TaskStatus.Open
    }
    }
    set(value) { status = value.name }
    }

    When you have enabled anonymous authentication in the Realm UI, users can immediately log into your app without providing any identifying information:

    val credentials: Credentials = Credentials.anonymous()
    app.loginAsync(credentials) {
    if (it.isSuccess) {
    Log.v("QUICKSTART", "Successfully authenticated anonymously.")
    val user: User? = app.currentUser()
    // interact with realm using your user object here
    } else {
    Log.e("QUICKSTART", "Failed to log in. Error: ${it.error}")
    }
    }

    Realm provides many additional ways to authenticate, register, and link users.

    Bulb IconTip

    Once you have enabled Realm Sync and authenticated a user, you can open a synced realm. Use the SyncConfiguration to control the specifics of how your application synchronizes data with MongoDB Realm, including the partition, how long to wait before a request times out, whether to allow synchronous reads or writes to a realm on the UI thread, and more.

    val partitionValue: String = "My Project"
    val config = SyncConfiguration.Builder(user, partitionValue)
    .build()
    val backgroundThreadRealm : Realm = Realm.getInstance(config)
    Bulb IconTip
    See also:

    Once you have opened a realm, you can modify the objects within that realm in a write transaction block.

    Important With Circle IconCreated with Sketch.Important
    Synchronous Reads and Writes on the UI Thread

    By default, you can only read or write to a realm in your application's UI thread using asynchronous transactions. That is, you can only use Realm methods whose name ends with the word Async in the main thread of your Android application unless you explicitly allow the use of synchronous methods.

    This restriction exists for the benefit of your application users: performing read and write operations on the UI thread can lead to unresponsive or slow UI interactions, so it's usually best to handle these operations either asynchronously or in a background thread. However, if your application requires the use of synchronous realm reads or writes on the UI thread, you can explicitly allow the use of synchronous methods with the following SyncConfiguration options:

    val config = RealmConfiguration.Builder()
    .allowQueriesOnUiThread(true)
    .allowWritesOnUiThread(true)
    .build()
    Realm.getInstanceAsync(config, object : Realm.Callback() {
    override fun onSuccess(realm: Realm) {
    Log.v(
    "EXAMPLE",
    "Successfully opened a realm with reads and writes allowed on the UI thread."
    )
    }
    })

    To create a new Task, instantiate an instance of the Task class and add it to the realm in a write block:

    val task : Task = Task("New Task", partitionValue)
    backgroundThreadRealm.executeTransaction { transactionRealm ->
    transactionRealm.insert(task)
    }

    You can retrieve a live collection of all items in the realm:

    // all tasks in the realm
    val tasks : RealmResults<Task> = backgroundThreadRealm.where<Task>().findAll()

    You can also filter that collection using a filter:

    // you can also filter a collection
    val tasksThatBeginWithN : List<Task> = tasks.where().beginsWith("name", "N").findAll()
    val openTasks : List<Task> = tasks.where().equalTo("status", TaskStatus.Open.name).findAll()

    To modify a task, update its properties in a write transaction block:

    val otherTask: Task = tasks[0]!!
    // all modifications to a realm must happen inside of a write block
    backgroundThreadRealm.executeTransaction { transactionRealm ->
    val innerOtherTask : Task = transactionRealm.where<Task>().equalTo("_id", otherTask._id).findFirst()!!
    innerOtherTask.status = TaskStatus.Complete.name
    }

    Finally, you can delete a task by calling the deleteFromRealm() method in a write transaction block:

    val yetAnotherTask: Task = tasks.get(0)!!
    val yetAnotherTaskId: ObjectId = yetAnotherTask._id
    // all modifications to a realm must happen inside of a write block
    backgroundThreadRealm.executeTransaction { transactionRealm ->
    val innerYetAnotherTask : Task = transactionRealm.where<Task>().equalTo("_id", yetAnotherTaskId).findFirst()!!
    innerYetAnotherTask.deleteFromRealm()
    }

    You can watch a realm, collection, or object for changes by attaching a custom OrderedRealmCollectionChangeListener with the addChangeListener() method:

    // all tasks in the realm
    val tasks : RealmResults<Task> = realm.where<Task>().findAllAsync()
    tasks.addChangeListener(OrderedRealmCollectionChangeListener<RealmResults<Task>> { collection, changeSet ->
    // process deletions in reverse order if maintaining parallel data structures so indices don't change as you iterate
    val deletions = changeSet.deletionRanges
    for (i in deletions.indices.reversed()) {
    val range = deletions[i]
    Log.v("QUICKSTART", "Deleted range: ${range.startIndex} to ${range.startIndex + range.length - 1}")
    }
    val insertions = changeSet.insertionRanges
    for (range in insertions) {
    Log.v("QUICKSTART", "Inserted range: ${range.startIndex} to ${range.startIndex + range.length - 1}")
    }
    val modifications = changeSet.changeRanges
    for (range in modifications) {
    Log.v("QUICKSTART", "Updated range: ${range.startIndex} to ${range.startIndex + range.length - 1}")
    }
    })

    Once logged in, you can log out:

    app.currentUser()?.logOutAsync() {
    if (it.isSuccess) {
    Log.v("QUICKSTART", "Successfully logged out.")
    } else {
    Log.e("QUICKSTART", "Failed to log out, error: ${it.error}")
    }
    }

    Run the complete example by replacing the appId with your realm app ID. If you're running this project in a fresh Android Studio project, you can copy and paste this file into your application's MainActivity -- just remember to:

    • change the package declaration so it matches your project
    • replace the App ID placeholder with your Realm app's App ID
    • update the import statements for Task and TaskStatus if you're using java
    MainActivity.kt
    package com.mongodb.realm.examples.kotlin
    import org.bson.types.ObjectId
    import android.os.Bundle
    import androidx.appcompat.app.AppCompatActivity
    import android.util.Log
    import io.realm.OrderedRealmCollectionChangeListener
    import io.realm.Realm
    import io.realm.RealmObject
    import io.realm.RealmResults
    import io.realm.annotations.PrimaryKey
    import io.realm.annotations.Required
    import io.realm.kotlin.where
    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.ExecutorService
    import java.util.concurrent.Executors
    import java.util.concurrent.FutureTask
    class MainActivity : AppCompatActivity() {
    lateinit var uiThreadRealm: Realm
    lateinit var app: App
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    Realm.init(this) // context, usually an Activity or Application
    val appID : String = YOUR_APP_ID;
    app = App(AppConfiguration.Builder(appID)
    .build())
    val credentials: Credentials = Credentials.anonymous()
    app.loginAsync(credentials) {
    if (it.isSuccess) {
    Log.v("QUICKSTART", "Successfully authenticated anonymously.")
    val user: User? = app.currentUser()
    val partitionValue: String = "My Project"
    val config = SyncConfiguration.Builder(user, partitionValue)
    .build()
    uiThreadRealm = Realm.getInstance(config)
    addChangeListenerToRealm(uiThreadRealm)
    val task : FutureTask<String> = FutureTask(BackgroundQuickStart(app.currentUser()!!), "test")
    val executorService: ExecutorService = Executors.newFixedThreadPool(2)
    executorService.execute(task)
    } else {
    Log.e("QUICKSTART", "Failed to log in. Error: ${it.error}")
    }
    }
    }
    fun addChangeListenerToRealm(realm : Realm) {
    // all tasks in the realm
    val tasks : RealmResults<Task> = realm.where<Task>().findAllAsync()
    tasks.addChangeListener(OrderedRealmCollectionChangeListener<RealmResults<Task>> { collection, changeSet ->
    // process deletions in reverse order if maintaining parallel data structures so indices don't change as you iterate
    val deletions = changeSet.deletionRanges
    for (i in deletions.indices.reversed()) {
    val range = deletions[i]
    Log.v("QUICKSTART", "Deleted range: ${range.startIndex} to ${range.startIndex + range.length - 1}")
    }
    val insertions = changeSet.insertionRanges
    for (range in insertions) {
    Log.v("QUICKSTART", "Inserted range: ${range.startIndex} to ${range.startIndex + range.length - 1}")
    }
    val modifications = changeSet.changeRanges
    for (range in modifications) {
    Log.v("QUICKSTART", "Updated range: ${range.startIndex} to ${range.startIndex + range.length - 1}")
    }
    })
    }
    override fun onDestroy() {
    super.onDestroy()
    // the ui thread realm uses asynchronous transactions, so we can only safely close the realm
    // when the activity ends and we can safely assume that those transactions have completed
    uiThreadRealm.close()
    app.currentUser()?.logOutAsync() {
    if (it.isSuccess) {
    Log.v("QUICKSTART", "Successfully logged out.")
    } else {
    Log.e("QUICKSTART", "Failed to log out, error: ${it.error}")
    }
    }
    }
    class BackgroundQuickStart(val user: User) : Runnable {
    override fun run() {
    val partitionValue: String = "My Project"
    val config = SyncConfiguration.Builder(user, partitionValue)
    .build()
    val backgroundThreadRealm : Realm = Realm.getInstance(config)
    val task : Task = Task("New Task", partitionValue)
    backgroundThreadRealm.executeTransaction { transactionRealm ->
    transactionRealm.insert(task)
    }
    // all tasks in the realm
    val tasks : RealmResults<Task> = backgroundThreadRealm.where<Task>().findAll()
    // you can also filter a collection
    val tasksThatBeginWithN : List<Task> = tasks.where().beginsWith("name", "N").findAll()
    val openTasks : List<Task> = tasks.where().equalTo("status", TaskStatus.Open.name).findAll()
    val otherTask: Task = tasks[0]!!
    // all modifications to a realm must happen inside of a write block
    backgroundThreadRealm.executeTransaction { transactionRealm ->
    val innerOtherTask : Task = transactionRealm.where<Task>().equalTo("_id", otherTask._id).findFirst()!!
    innerOtherTask.status = TaskStatus.Complete.name
    }
    val yetAnotherTask: Task = tasks.get(0)!!
    val yetAnotherTaskId: ObjectId = yetAnotherTask._id
    // all modifications to a realm must happen inside of a write block
    backgroundThreadRealm.executeTransaction { transactionRealm ->
    val innerYetAnotherTask : Task = transactionRealm.where<Task>().equalTo("_id", yetAnotherTaskId).findFirst()!!
    innerYetAnotherTask.deleteFromRealm()
    }
    // because this background thread uses synchronous realm transactions, at this point all
    // transactions have completed and we can safely close the realm
    backgroundThreadRealm.close()
    }
    }
    }
    enum class TaskStatus(val displayName: String) {
    Open("Open"),
    InProgress("In Progress"),
    Complete("Complete"),
    }
    open class Task(_name: String = "Task", project: String = "My Project") : RealmObject() {
    @PrimaryKey
    var _id: ObjectId = ObjectId()
    var name: String = _name
    @Required
    var status: String = TaskStatus.Open.name
    var statusEnum: TaskStatus
    get() {
    // because status is actually a String and another client could assign an invalid value,
    // default the status to "Open" if the status is unreadable
    return try {
    TaskStatus.valueOf(status)
    } catch (e: IllegalArgumentException) {
    TaskStatus.Open
    }
    }
    set(value) { status = value.name }
    }

    Running the above code should produce output resembling the following:

    Successfully authenticated anonymously.
    Updated range: 0 to 1
    Deleted range: 0 to 1
    Successfully logged out.
    Give Feedback