完了までの推定時間: 30分( Kotlin の経験により異なります)
Realm はKotlin SDKを提供しており、Jetpack Compose を使用してKotlinで Android モバイルアプリケーションを作成できます。このチュートリアルでは、ToDo アイテム リスト管理アプリケーションの作成を説明する、kotlin.todo.flex という名前のKotlin Flexible Sync テンプレート アプリに基づいています。このアプリケーションを使用すると、ユーザーは次のことが可能になります。
- メールを新しいユーザー アカウントとして登録します。 
- メールとパスワードを使用してアカウントにサインインします(その後サインアウトします)。 
- 自分のタスクを表示、作成、変更、削除します。 
- ユーザーが所有者でない場合でも、すべてのタスクを表示します。 
また、テンプレートアプリには、デバイスがオフラインモードになっていることをシミュレートするトグルが用意されています。 このトグルを使用すると、シミュレーターで Device Sync 機能をすばやくテストして、インターネットに接続していないユーザーをエミュレートできます。 ただし、本番アプリケーションではこのトグルを削除する可能性が高いでしょう。
このチュートリアルでは、テンプレート アプリに機能を追加します。 既存のItemモデルに新しいPriorityフィールドを追加し、 Flexible Sync サブスクライブを更新して、優先順位の範囲内のアイテムのみを表示します。 この例では、テンプレート アプリを独自のニーズに合わせて調整する方法について説明しています。
学習目的
このチュートリアルでは、テンプレート アプリを独自のニーズに合わせて調整する方法について説明します。
このチュートリアルでは、次の方法を学習します。
- Realm オブジェクトモデルを重大じゃない変更で更新します。 
- Device Sync サブスクライブを更新します。 
- 同期されるデータを変更するには、サーバー上の Device Sync 構成にクエリ可能なフィールドを追加します。 
注意
クイック スタートを確認する
ガイド付きチュートリアルに従うのではなく、独自のアプリケーションを使い始める場合は、 Kotlin クイック スタートを確認できます。 コピー可能なコード サンプルと、Atlas App Services バックエンドを設定するために必要な重要な情報が含まれています。
前提条件
- Android Studio Bundlebee 2021.1.1またはそれ以上 
- JDK 11 以上 
- Kotlin Plugin for Android Studio バージョン 1.6.10 以上 
- サポート対象の CPU アーキテクチャが使用されている Android Virtual Device(AVD) 
- このチュートリアルは、テンプレート アプリから始めます。 テンプレート アプリを作成するには、 Atlas アカウント、 API キー、 App Services CLI が必要です。 - Atlas アカウントの作成の詳細については、「 Atlas の使用開始」ドキュメントを参照してください。 このチュートリアルでは、無料階層クラスターを持つ Atlas アカウントが必要です。 
- ログインする MongoDB Cloud アカウントの Atlas API キーも必要です。 App Services CLI を使用してテンプレート アプリを作成するには、プロジェクト オーナーである必要があります。 
- App Services CLI のインストールについて詳しくは、「 App Services CLI のインストール」を参照してください。 インストール後、Atlas プロジェクトの API キーを使用して「 login 」コマンドを実行します。 
 
テンプレートを使用して開始
このチュートリアルは、 kotlin.todo.flexという名前の Kotlin SDK Flexible Sync テンプレート アプリを基本にして作成されており、 デフォルトのアプリから始めて、新しい機能について説明しています。
テンプレート アプリの詳細については、「テンプレート アプリ 」を参照してください。
Atlas アカウントがまだない場合は、テンプレート アプリを配置するためにサインアップしてください。
「アプリの作成」ガイドに記載されている手順に従い、 Create App from Templateを選択します。 Real-time Syncテンプレートを選択します。 これにより、Device Sync テンプレート App Services App クライアントの 1 つで使用するために事前構成された App Services App が作成されます。
テンプレート アプリを作成すると、UI に Get the Front-end Code for your Template というラベルの付いたモーダルが表示されます。このモーダルには、テンプレート アプリのクライアントコードを .zip ファイルとしてダウンロードする方法や、App Services CLI を使用してクライアントを取得する方法が表示されます。
.zip または App Services CLI メソッドを選択した後、画面の指示に従ってクライアント コードを取得します。このチュートリアルでは、Kotlin (Android) クライアント コードを選択します。
注意
デフォルトの Windows ZIP ユーティリティで、.zip ファイルが空白ファイルとして表示される。この問題が発生した場合は、利用可能なサードパーティの zip プログラムのいずれかを使用してください。
appservices apps createコマンドでバックエンドを設定し、 Kotlin テンプレート アプリを作成してこのチュートリアルのベースとして使用します。
ターミナル ウィンドウで次のコマンドを実行して、「Myチュートリアル App」という名前のアプリを作成し、環境が「開発」(本番環境やQAではなく)に設定されているUS-VAリージョンに配置されます。
appservices app create \   --name MyTutorialApp \   --template kotlin.todo.flex \   --deployment-model global \   --environment development 
このコマンドは、 --nameフラグの値と同じ名前で現在のパスに新しいディレクトリを作成します。
Device Syncクライアントコードを含む Githubリポジトリをフォークしてクローンできます。Kotlinクライアントコードは https://github.com/mongodb/template-app-kotlin-todo で入手できます。
このプロセスを使用してクライアントのコードを取得する場合は、クライアントで使用するテンプレート アプリを作成する必要があります。 「テンプレート アプリの作成」 の手順に従って、Atlas App Services UI、App Services CLI、または管理 API を使用して Device Sync テンプレート アプリを作成します。
テンプレート アプリの設定
アプリ構造の探索
Android Studio がプロジェクトをインデックス化している間に、プロジェクト組織を調べます。 app/java/com.mongodb.appディレクトリ内には、注目に値するファイルがいくつかあります。
| ファイル | 目的 | 
|---|---|
| ComposeItemActivity.kt | 配置を定義し、Realm のオープン、Realm へのアイテムの書込み、ユーザーのログアウト、Realm を閉じるための機能を提供する アクティビティ クラス。 | 
| ComposLoginActivity.kt | レイアウトを定義し、ユーザーを登録してユーザーをログインするための機能を提供するアクティビティ クラス。 | 
| TemplateApp.kt | App Services Appを初期化するクラス。 | 
このチュートリアルでは、次のファイルを操作します。
| ファイル | 目的 | 
|---|---|
| Item.kt | 
 | 
| AddItem.kt | 
 | 
| AddItemViewModel.kt | 
 | 
| SyncRepository.kt | 
 | 
| String.xl | 
 | 
バックエンドの確認
Atlas App Servicesにログインします。 Data Servicesタブで、 Browse Collectionsをクリックします。 データベースのリストで、 todoデータベース、 Itemコレクションを検索して展開します。 このコレクションで作成したドキュメントが表示されます。
アプリケーションを変更する
新しいプロパティの追加
モデルへの新しいプロパティの追加
すべて期待どおりに動作していることが確認できたため、変更を追加できます。 このチュートリアルでは、各アイテムに「優先順位」プロパティを追加し、アイテムに優先順位をつけてフィルタリングできるようにしました。 優先順位プロパティをPriorityLevel列挙型にマッピングして、可能な値を制限します。各列挙型の序数を使用して優先順位整数に対応するようにします。これにより、後で数値優先レベルに基づいてクエリを実行できるようになります。
これを行うには、次の手順に従います。
- app/java/com.mongodb.app/domainフォルダ内で、- Itemクラスファイルを開きます。
- 可能な値を制限するには、 - PriorityLevel列挙を追加します。 また、- Itemクラスに- priorityプロパティを追加します。これにより、デフォルトの優先順位が 3 に設定されます。これは、優先順位の低い Todo アイテムであることを示します。domain/Item.kt- // ... imports - enum class PriorityLevel() { - Severe, // priority 0 - High, // priority 1 - Medium, // priority 2 - Low // priority 3 - } - class Item() : RealmObject { - var _id: ObjectId = ObjectId.create() - var isComplete: Boolean = false - var summary: String = "" - var owner_id: String = "" - var priority: Int = PriorityLevel.Low.ordinal - constructor(ownerId: String = "") : this() { - owner_id = ownerId - } - // ... equals() and hashCode() functions - } 
新しいアイテムの作成時の優先順位の設定
- ui/tasksフォルダから、- AddItem.ktファイルを開きます。 このファイルは、ユーザーが [+] ボタンをクリックして新しい Todo 項目を追加するときに表示される UI の複合関数を定義します。
- まず、 - package com.mongodb.appの下に次のインポートを追加します。ui/tasks/AddItem.kt- import androidx.compose.foundation.layout.fillMaxWidth - import androidx.compose.foundation.layout.padding - import androidx.compose.material3.DropdownMenuItem - import androidx.compose.material3.ExposedDropdownMenuBox - import androidx.compose.ui.Modifier - import androidx.compose.ui.unit.dp - import com.mongodb.app.domain.PriorityLevel 
- ここで、ユーザーが優先順位列挙型を使用可能な値として使用してリストから優先度レベルを選択できるようにする - AddItemPromptにドロップダウン フィールドを追加します。ui/tasks/AddItem.kt- // ... imports - fun AddItemPrompt(viewModel: AddItemViewModel) { - AlertDialog( - containerColor = Color.White, - onDismissRequest = { - viewModel.closeAddTaskDialog() - }, - title = { Text(stringResource(R.string.add_item)) }, - text = { - Column { - Text(stringResource(R.string.enter_item_name)) - TextField( - colors = ExposedDropdownMenuDefaults.textFieldColors(containerColor = Color.White), - value = viewModel.taskSummary.value, - maxLines = 2, - onValueChange = { - viewModel.updateTaskSummary(it) - }, - label = { Text(stringResource(R.string.item_summary)) } - ) - val priorities = PriorityLevel.values() - ExposedDropdownMenuBox( - modifier = Modifier.padding(16.dp), - expanded = viewModel.expanded.value, - onExpandedChange = { viewModel.open() }, - ) { - TextField( - readOnly = true, - value = viewModel.taskPriority.value.name, - onValueChange = {}, - label = { Text(stringResource(R.string.item_priority)) }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = viewModel.expanded.value) }, - colors = ExposedDropdownMenuDefaults.textFieldColors(), - modifier = Modifier - .fillMaxWidth() - .menuAnchor() - ) - ExposedDropdownMenu( - expanded = viewModel.expanded.value, - onDismissRequest = { viewModel.close() } - ) { - priorities.forEach { - DropdownMenuItem( - text = { Text(it.name) }, - onClick = { - viewModel.updateTaskPriority(it) - viewModel.close() - } - ) - } - } - } - } - }, - // ... buttons - ) - } - Android Studio はいくつかのエラーを識別します。 次のステップでは、関連する関数を追加して、これらを修正します。 
- 次に、ドロップダウン フィールド ラベルを string リソースとして定義します。 - res/values/strings.xmlファイルを開き、「resource」要素を閉じる前に以下を追加します。res/values/strings.xl- <string name="item_priority">Item Priority</string> 
- presentation/tasksフォルダー内で、- AddItemViewModel.ktファイルを開きます。 ここで、新しいドロップダウン フィールドに関連するビジネス ロジックを追加します。- package com.mongodb.appの下に- PriorityLevelインポートを追加し、ドロップダウン内の状態変更を処理するために必要な- AddItemViewModelクラスに変数と関数を追加します。presentation/tasks/AddItemViewModel.kt- // ... imports - import com.mongodb.app.domain.PriorityLevel - // ... events - class AddItemViewModel( - private val repository: SyncRepository - ) : ViewModel() { - private val _addItemPopupVisible: MutableState<Boolean> = mutableStateOf(false) - val addItemPopupVisible: State<Boolean> - get() = _addItemPopupVisible - private val _taskSummary: MutableState<String> = mutableStateOf("") - val taskSummary: State<String> - get() = _taskSummary - private val _taskPriority: MutableState<PriorityLevel> = mutableStateOf(PriorityLevel.Low) - val taskPriority: State<PriorityLevel> - get() = _taskPriority - private val _expanded: MutableState<Boolean> = mutableStateOf(false) - val expanded: State<Boolean> - get() = _expanded - private val _addItemEvent: MutableSharedFlow<AddItemEvent> = MutableSharedFlow() - val addItemEvent: Flow<AddItemEvent> - get() = _addItemEvent - fun openAddTaskDialog() { - _addItemPopupVisible.value = true - } - fun closeAddTaskDialog() { - cleanUpAndClose() - } - fun updateTaskSummary(taskSummary: String) { - _taskSummary.value = taskSummary - } - fun updateTaskPriority(taskPriority: PriorityLevel) { - _taskPriority.value = taskPriority - } - fun open() { - _expanded.value = true - } - fun close() { - _expanded.value = false - } - // addTask() and cleanUpAndClose() functions - } - ここで、 - addTask()関数と- cleanUpAndClose()関数を更新して新しい- taskPriorityパラメーターを含め、優先順位情報でメッセージを更新し、アイテム追加ビューが閉じられたら優先順位フィールドを低にリセットします。- fun addTask() { - CoroutineScope(Dispatchers.IO).launch { - runCatching { - repository.addTask(taskSummary.value, taskPriority.value) - }.onSuccess { - withContext(Dispatchers.Main) { - _addItemEvent.emit(AddItemEvent.Info("Task '$taskSummary' with priority '$taskPriority' added successfully.")) - } - }.onFailure { - withContext(Dispatchers.Main) { - _addItemEvent.emit(AddItemEvent.Error("There was an error while adding the task '$taskSummary'", it)) - } - } - cleanUpAndClose() - } - } - private fun cleanUpAndClose() { - _taskSummary.value = "" - _taskPriority.value = PriorityLevel.Low - _addItemPopupVisible.value = false - } 
- 最後に、 - dataフォルダーから- SyncRepository.ktファイルを開き、アイテムを Realm に書込む- addTask()関数で同じ変更を反映します。- まず、 - package com.mongodb.appの下に- PriorityLevelインポートを追加し、- addTask()関数を更新して- taskPriorityをパラメーターとして渡し、- priorityフィールドを整数として Realm に書込みます(列挙序数を使用)。data/SyncRepository.kt- // ... imports - import com.mongodb.app.domain.PriorityLevel - interface SyncRepository { - // ... Sync functions - suspend fun addTask(taskSummary: String, taskPriority: PriorityLevel) - // ... Sync functions - } - class RealmSyncRepository( - onSyncError: (session: SyncSession, error: SyncException) -> Unit - ) : SyncRepository { - // ... variables and SyncConfiguration initializer - // ... Sync functions - override suspend fun addTask(taskSummary: String, taskPriority: PriorityLevel) { - val task = Item().apply { - owner_id = currentUser.id - summary = taskSummary - priority = taskPriority.ordinal - } - realm.write { - copyToRealm(task) - } - } - override suspend fun updateSubscriptions(subscriptionType: SubscriptionType) { - realm.subscriptions.update { - removeAll() - val query = when (subscriptionType) { - SubscriptionType.MINE -> getQuery(realm, SubscriptionType.MINE) - SubscriptionType.ALL -> getQuery(realm, SubscriptionType.ALL) - } - add(query, subscriptionType.name) - } - } - // ... additional Sync functions - } - class MockRepository : SyncRepository { - override fun getTaskList(): Flow<ResultsChange<Item>> = flowOf() - override suspend fun toggleIsComplete(task: Item) = Unit - override suspend fun addTask(taskSummary: String, taskPriority: PriorityLevel) = Unit - override suspend fun updateSubscriptions(subscriptionType: SubscriptionType) = Unit - override suspend fun deleteTask(task: Item) = Unit - override fun getActiveSubscriptionType(realm: Realm?): SubscriptionType = SubscriptionType.ALL - override fun pauseSync() = Unit - override fun resumeSync() = Unit - override fun isTaskMine(task: Item): Boolean = task.owner_id == MOCK_OWNER_ID_MINE - override fun close() = Unit - // ... companion object - } 
およびテストの実行
この時点で、アプリケーションを再実行できます。 このチュートリアルの前半で作成したアカウントを使用してログインします。 過去に作成した 1 つのアイテムが表示されます。 新しいアイテムを追加すると、優先順位を設定できるようになります。 優先順位をHighに選択し、アイテムを保存します。
ブラウザで Atlas データページに切り替え、 Itemコレクションを更新します。 これでpriorityフィールドが追加され、 1に設定された新しいアイテムが表示されます。 既存のアイテムにはpriorityフィールドはありません。

注意
同期が中断されなかった理由
Realm オブジェクトにプロパティを追加しても重大な変更ではないため、クライアントをリセットする必要はありません。 テンプレート アプリでは開発モードが有効になっているため、クライアント Realm オブジェクトへの変更はサーバー側のスキーマに反映されます。 詳しくは、「開発モードとデータモデルの更新 」を参照してください。
サブスクリプションを変更する
サブスクライブを更新する
app/java/com.mongodb.app/dataフォルダー内で、 SyncRepository.ktファイルを開き、Flexible Sync サブスクリプションを定義します。 サブスクリプションは、ユーザーのデバイスおよびアカウントと同期するドキュメントを定義します。 getQuery()関数を見つけます。 現在 2 つのサブスクライブをサブスクライブしていることがわかります。
- MINE:- ownerIdプロパティが認証されたユーザーと一致するすべてのドキュメント。
- ALL: すべてのユーザーからのすべてのドキュメント。
MINEサブスクライブを更新して、優先度が「高」または「重要」とマークされたアイテムのみを同期するようにしたいと考えています。
ご存知のとおり、 priorityフィールドのタイプはintで、最高優先順位("Severe")の値は 0、最低優先順位("Low")の値は 3 です。次が可能です:整数と優先順位プロパティを直接比較します。 そのためには、次のように、優先順位が PerformanceLevel.High(または 1)より小さいドキュメントを含むように RQL ステートメントを編集します。
private fun getQuery(realm: Realm, subscriptionType: SubscriptionType): RealmQuery<Item> =    when (subscriptionType) {          SubscriptionType.MINE -> realm.query("owner_id == $0 AND priority <= ${PriorityLevel.High.ordinal}", currentUser.id)          SubscriptionType.ALL -> realm.query()    } 
また、アプリを開くたびに同期するドキュメントを再計算するようにサブスクライブ クエリを強制します。
そのためには、アプリケーションの起動時に呼び出されるSyncConfiguration.Builder().initialSubscriptions()関数を見つけます。 まず、 reRunOnOpenパラメータ設定をtrueに追加し、次にupdateExistingをtrueに設定します。これにより、既存のクエリを更新できるようになります。
config = SyncConfiguration.Builder(currentUser, setOf(Item::class))    .initialSubscriptions(rerunOnOpen = true) { realm ->        // Subscribe to the active subscriptionType - first time defaults to MINE        val activeSubscriptionType = getActiveSubscriptionType(realm)        add(getQuery(realm, activeSubscriptionType), activeSubscriptionType.name, updateExisting = true)    }    .errorHandler { session: SyncSession, error: SyncException ->        onSyncError.invoke(session, error)    }    .waitForInitialRemoteData()    .build() 
およびテストの実行
アプリケーションを再度実行します。このチュートリアルの前半で作成したアカウントを使用してログインします。
Realm がドキュメントコレクションを再同期する最初の瞬間、作成した優先順位の高い新しいアイテムが表示されます。
Tip
開発者モードが有効になっている場合にサブスクリプションを変更する
このチュートリアルでは、サブスクリプションを変更し、優先順位フィールドを初めてクエリすると、フィールドは Device Sync Collection Queryable Fieldsに自動的に追加されます。 これは、テンプレート アプリで開発モードがデフォルトで有効になっているために発生します。 開発モードが有効になっていない場合、クライアント側の同期クエリで使用するには、フィールドをクエリ可能なフィールドとして手動で追加する必要があります。
詳細については、「クエリ可能なフィールド 」を参照してください。
機能をさらにテストしたい場合は、さまざまな優先順位のアイテムを作成できます。優先順位が「高」より低いアイテムを追加しようとすると、権限がないことを示す Toast メッセージが表示されます。また、Logcat を使用してログを確認すると、項目が「正常に追加された」ことを示すメッセージと、それに続く同期エラーが表示されます。
ERROR "Client attempted a write that is outside of permissions or query filters; it has been reverted" 
これは、このシナリオでは Realm はアイテムをローカルに作成し、バックエンドと同期した後、サブスクライブ ルールを満たしていないため書込みを元に戻すためです。
また、最初に作成したドキュメントは優先順位がnullになっているため、同期されないことにも注意してください。 このアイテムを同期する場合は、Atlas UI でドキュメントを編集し、優先順位フィールドに値を追加します。
まとめ
既存の Realm オブジェクトにプロパティを追加することは重大じゃない変更であり、 開発モード によってスキーマの変更がサーバー側に反映されるようになります。
次のステップ
- Kotlin SDKドキュメントをお読みください。 
- Reddit または Stack Overflow のMongoDBコミュニティに参加して、他のMongoDB開発者や技術専門家から学びます。 
- エンジニアリング プロジェクトと専門家が提供するサンプルプロジェクトを探索します。 
注意
フィードバックの共有
ではどのようにGoたか。 ページ右下にある Rate this page ウィジェットを使用して、有効性を評価します。または、問題が発生した場合はGithubリポジトリに問題をファイル。