使用同步快速入门 - Java SDK
提示
本指南使用 Device Sync
本指南帮助您开始使用与应用后端通信的 Android应用程序程序。 该应用程序提供 Sync 、 Realm函数和用户管理等功能。 如果您的应用程序只需要本地数据库功能,请查看快速入门(仅限本地)指南。
此页面包含快速将 Atlas App Services 集成到您的应用程序中的信息。 开始之前,请确保您已:
初始化 Realm
在应用程序中使用 Realm 之前,您必须初始化 Realm 库。 您的应用程序应该在每次运行时初始化 Realm 一次。
要初始化Realm库,请为Realm.init()
静态函数提供一个 Android context
。 您可以提供活动、片段或应用程序context
进行初始化,而行为不会有任何差异。 您可以在 应用程序子类onCreate()
的 方法中初始化Realm库 以确保每次应用程序运行时仅初始化Realm一次。
Realm.init(this); // context, usually an Activity or Application
Realm.init(this) // context, usually an Activity or Application
提示
在 Android 清单中注册您的应用程序子类
如果创建自己的Application
子类,则必须将其添加到应用程序的AndroidManifest.xml
中才能执行自定义应用程序逻辑。 设置清单应用程序定义的android.name
属性,确保当用户启动您的应用程序时,Android 会先于任何其他类实例化您的Application
子类。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mongodb.example"> <application android:name=".MyApplicationSubclass" ... /> </manifest>
初始化 App Services
要使用身份验证和同步等 App Services 功能,请使用您的App ID 访问 App Services 应用。您可在 App Services 用户界面中找到您的 App ID。
app = new App(new AppConfiguration.Builder(appID) .build());
val appID : String = YOUR_APP_ID; app = App(AppConfiguration.Builder(appID) .build())
注意
Android Studio 错误?
如果 Android Studio 无法识别Realm
、 App
或AppConfiguration
类型,则 Gradle 构建配置可能有问题。 要解决此问题,请执行以下操作:
使用清理项目
Build > Clean Project
使用
Build > Rebuild Project
根据更新的build.gradle
文件重建项目重新访问“安装 Java SDK”指南,确保正确安装依赖项。
定义对象模型
您的应用程序的Realm 数据模型定义了存储在 Realm 中以及与 App Services 之间同步的数据结构。您可以通过两种方式定义应用程序的 Realm 数据模型:
通过App Services中的模式。
本快速入门使用后一种方法,即使用移动应用程序代码中的类来定义模式。 要以这种方式定义应用的对象模型,您需要启用开发模式。
启用开发模式后,将以下类定义添加到应用程序代码中:
import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; import org.bson.types.ObjectId; public class Task extends RealmObject { private ObjectId _id = new ObjectId(); private String name = "Task"; private String status = TaskStatus.Open.name(); public void setStatus(TaskStatus status) { this.status = status.name(); } public String getStatus() { return this.status; } public ObjectId get_id() { return _id; } public void set_id(ObjectId _id) { this._id = _id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Task(String _name) { this.name = _name; } public Task() {} }
public enum TaskStatus { Open("Open"), InProgress("In Progress"), Complete("Complete"); String displayName; TaskStatus(String displayName) { this.displayName = displayName; } }
enum class TaskStatus(val displayName: String) { Open("Open"), InProgress("In Progress"), Complete("Complete"), } open class Task(_name: String = "Task", project: String = "My Project") : RealmObject() { var _id: ObjectId = ObjectId() var name: String = _name 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 } }
验证用户身份
在App Services用户界面中启用匿名身份验证后,用户无需提供任何身份信息即可立即日志您的应用:
Credentials credentials = Credentials.anonymous(); app.loginAsync(credentials, result -> { if (result.isSuccess()) { Log.v("QUICKSTART", "Successfully authenticated anonymously."); User user = app.currentUser(); String partitionValue = "My Project"; SyncConfiguration config = new SyncConfiguration.Builder( user, partitionValue) .build(); uiThreadRealm = Realm.getInstance(config); addChangeListenerToRealm(uiThreadRealm); FutureTask<String> task = new FutureTask(new BackgroundQuickStart(app.currentUser()), "test"); ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.execute(task); } else { Log.e("QUICKSTART", "Failed to log in. Error: " + result.getError()); } });
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}") } }
Realm 提供了许多其他方法来对用户进行身份验证、注册和链接。
打开 Realm
启用 Sync并对用户进行身份验证后,您可以打开 Synced 域 。 使用SyncConfiguration
控制应用程序如何与App Services同步数据的细节,包括超时、用户界面线程上的同步读取和写入等等。
String partitionValue = "My Project"; SyncConfiguration config = new SyncConfiguration.Builder( user, partitionValue) .build(); Realm backgroundThreadRealm = Realm.getInstance(config);
val partitionValue: String = "My Project" val config = SyncConfiguration.Builder(user, partitionValue) .build() val backgroundThreadRealm : Realm = Realm.getInstance(config)
创建、读取、更新和删除对象
打开域后,您可以在 写事务(write transaction) 区块中修改该域内的 对象 。
重要
UI 线程上的同步读写
默认情况下,只能使用异步事务在应用程序的用户界面线程中读取或写入域。也就是说,除非您明确允许使用同步方法,否则您只能在 Android 应用程序的主线程中使用名称以单词 Async
结尾的 Realm
方法。
此限制是为了应用程序用户的利益:在 UI 线程上执行读写操作,可能导致 UI 交互无响应或速度缓慢,所以通常来说,最好以异步方式或在后台线程中处理这些操作。但是,如果应用程序需要在 UI 线程上使用同步 Realm 读取或写入,则可以通过以下 SyncConfiguration
选项明确支持使用同步方法:
SyncConfiguration config = new SyncConfiguration.Builder(app.currentUser(), PARTITION) .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build(); Realm.getInstanceAsync(config, new Realm.Callback() { public void onSuccess(Realm realm) { Log.v( "EXAMPLE", "Successfully opened a realm with reads and writes allowed on the UI thread." ); } });
val config = SyncConfiguration.Builder(app.currentUser(), PARTITION) .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.") } })
要创建新的Task
,请实例化Task
类的实例并将其添加到写入区块中的 Realm:
Task task = new Task("New Task"); backgroundThreadRealm.executeTransaction (transactionRealm -> { transactionRealm.insert(task); });
val task : Task = Task("New Task", partitionValue) backgroundThreadRealm.executeTransaction { transactionRealm -> transactionRealm.insert(task) }
您可以检索域中所有项目的实时集合:
// all tasks in the realm RealmResults<Task> tasks = backgroundThreadRealm.where(Task.class).findAll();
// all tasks in the realm val tasks : RealmResults<Task> = backgroundThreadRealm.where<Task>().findAll()
您还可以使用过滤过滤该集合:
// you can also filter a collection RealmResults<Task> tasksThatBeginWithN = tasks.where().beginsWith("name", "N").findAll(); RealmResults<Task> openTasks = tasks.where().equalTo("status", TaskStatus.Open.name()).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()
要修改任务,请在写事务区块中更新其属性:
Task otherTask = tasks.get(0); // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction( transactionRealm -> { Task innerOtherTask = transactionRealm.where(Task.class).equalTo("_id", otherTask.get_id()).findFirst(); innerOtherTask.setStatus(TaskStatus.Complete); });
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 }
最后,您可以通过在写事务(write transaction)区块中调用deleteFromRealm()
方法来删除除任务:
Task yetAnotherTask = tasks.get(0); ObjectId yetAnotherTaskId = yetAnotherTask.get_id(); // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction( transactionRealm -> { Task innerYetAnotherTask = transactionRealm.where(Task.class).equalTo("_id", yetAnotherTaskId).findFirst(); innerYetAnotherTask.deleteFromRealm(); });
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() }
注意更改
您可以通过使用addChangeListener()
方法附加自定义OrderedRealmCollectionChangeListener
来监视域、集合或对象的更改:
// all tasks in the realm RealmResults<Task> tasks = uiThreadRealm.where(Task.class).findAllAsync(); tasks.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Task>>() { public void onChange(RealmResults<Task> collection, OrderedCollectionChangeSet changeSet) { // process deletions in reverse order if maintaining parallel data structures so indices don't change as you iterate OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges(); for (OrderedCollectionChangeSet.Range range : deletions) { Log.v("QUICKSTART", "Deleted range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range range : insertions) { Log.v("QUICKSTART", "Inserted range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges(); for (OrderedCollectionChangeSet.Range range : modifications) { Log.v("QUICKSTART", "Updated range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } } });
// 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}") } })
登出
登录后即可注销:
app.currentUser().logOutAsync(result -> { if (result.isSuccess()) { Log.v("QUICKSTART", "Successfully logged out."); } else { Log.e("QUICKSTART", "Failed to log out, error: " + result.getError()); } });
app.currentUser()?.logOutAsync() { if (it.isSuccess) { Log.v("QUICKSTART", "Successfully logged out.") } else { Log.e("QUICKSTART", "Failed to log out, error: ${it.error}") } }
完整示例
将 appId 替换为您的 域 应用程序 ID,运行完整示例。如果您在新的 Android Studio 项目中运行此项目,则可以将此文件复制并粘贴到应用程序的MainActivity
中,但请记住:
更改包声明,使其与您的项目匹配
将 应用程序 ID 占位符替换为您的应用的 应用程序 ID
如果您使用的是 Java,请更新
Task
和TaskStatus
的import
语句
import io.realm.RealmObject; import io.realm.annotations.PrimaryKey; import io.realm.annotations.RealmClass; import io.realm.annotations.Required; import org.bson.types.ObjectId; public class Task extends RealmObject { private ObjectId _id = new ObjectId(); private String name = "Task"; private String status = TaskStatus.Open.name(); public void setStatus(TaskStatus status) { this.status = status.name(); } public String getStatus() { return this.status; } public ObjectId get_id() { return _id; } public void set_id(ObjectId _id) { this._id = _id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Task(String _name) { this.name = _name; } public Task() {} }
public enum TaskStatus { Open("Open"), InProgress("In Progress"), Complete("Complete"); String displayName; TaskStatus(String displayName) { this.displayName = displayName; } }
import io.realm.OrderedCollectionChangeSet; 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.RealmResults; 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.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import com.mongodb.realm.examples.model.Task; import com.mongodb.realm.examples.model.TaskStatus; public class MainActivity extends AppCompatActivity { Realm uiThreadRealm; App app; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Realm.init(this); // context, usually an Activity or Application app = new App(new AppConfiguration.Builder(appID) .build()); Credentials credentials = Credentials.anonymous(); app.loginAsync(credentials, result -> { if (result.isSuccess()) { Log.v("QUICKSTART", "Successfully authenticated anonymously."); User user = app.currentUser(); String partitionValue = "My Project"; SyncConfiguration config = new SyncConfiguration.Builder( user, partitionValue) .build(); uiThreadRealm = Realm.getInstance(config); addChangeListenerToRealm(uiThreadRealm); FutureTask<String> task = new FutureTask(new BackgroundQuickStart(app.currentUser()), "test"); ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.execute(task); } else { Log.e("QUICKSTART", "Failed to log in. Error: " + result.getError()); } }); } private void addChangeListenerToRealm(Realm realm) { // all tasks in the realm RealmResults<Task> tasks = uiThreadRealm.where(Task.class).findAllAsync(); tasks.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Task>>() { public void onChange(RealmResults<Task> collection, OrderedCollectionChangeSet changeSet) { // process deletions in reverse order if maintaining parallel data structures so indices don't change as you iterate OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges(); for (OrderedCollectionChangeSet.Range range : deletions) { Log.v("QUICKSTART", "Deleted range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); for (OrderedCollectionChangeSet.Range range : insertions) { Log.v("QUICKSTART", "Inserted range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges(); for (OrderedCollectionChangeSet.Range range : modifications) { Log.v("QUICKSTART", "Updated range: " + range.startIndex + " to " + (range.startIndex + range.length - 1)); } } }); } protected void 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(result -> { if (result.isSuccess()) { Log.v("QUICKSTART", "Successfully logged out."); } else { Log.e("QUICKSTART", "Failed to log out, error: " + result.getError()); } }); } public class BackgroundQuickStart implements Runnable { User user; public BackgroundQuickStart(User user) { this.user = user; } public void run() { String partitionValue = "My Project"; SyncConfiguration config = new SyncConfiguration.Builder( user, partitionValue) .build(); Realm backgroundThreadRealm = Realm.getInstance(config); Task task = new Task("New Task"); backgroundThreadRealm.executeTransaction (transactionRealm -> { transactionRealm.insert(task); }); // all tasks in the realm RealmResults<Task> tasks = backgroundThreadRealm.where(Task.class).findAll(); // you can also filter a collection RealmResults<Task> tasksThatBeginWithN = tasks.where().beginsWith("name", "N").findAll(); RealmResults<Task> openTasks = tasks.where().equalTo("status", TaskStatus.Open.name()).findAll(); Task otherTask = tasks.get(0); // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction( transactionRealm -> { Task innerOtherTask = transactionRealm.where(Task.class).equalTo("_id", otherTask.get_id()).findFirst(); innerOtherTask.setStatus(TaskStatus.Complete); }); Task yetAnotherTask = tasks.get(0); ObjectId yetAnotherTaskId = yetAnotherTask.get_id(); // all modifications to a realm must happen inside of a write block backgroundThreadRealm.executeTransaction( transactionRealm -> { Task innerYetAnotherTask = transactionRealm.where(Task.class).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(); } } }
import org.bson.types.ObjectId import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import android.util.Log import com.mongodb.realm.examples.YOUR_APP_ID 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() { var _id: ObjectId = ObjectId() var name: String = _name 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 } }
输出
运行上述代码应产生类似以下内容的输出:
Successfully authenticated anonymously. Updated range: 0 to 1 Deleted range: 0 to 1 Successfully logged out.