Docs Menu
Docs Home
/ /
Atlas App Services

チュートリアル: Atlas Device Sync for Kotlin

項目一覧

  • 学習目的
  • 前提条件
  • テンプレートを使用して開始
  • テンプレート アプリの設定
  • アプリを開く
  • アプリ構造の探索
  • アプリを実行する
  • バックエンドの確認
  • アプリケーションを変更する
  • 新しいプロパティの追加
  • モデルへの新しいプロパティの追加
  • 新しいアイテムの作成時の優先順位の設定
  • およびテストの実行
  • サブスクリプションを変更する
  • サブスクライブを更新する
  • およびテストの実行
  • まとめ
  • 次のステップ

完了までの推定時間: 30分( Kotlin の経験により異なります)

Realm は Kotlin SDK を提供しており、 J件名 を使用して 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 バンドル式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 テンプレート アプリを作成します。

1

Android Studio で、 kotlin.todo.flexフォルダーを開きます。

クライアントを .zip ファイルとしてダウンロードした場合、またはクライアントGithubリポジトリをクローンした場合は、クライアント内の適切な場所にApp Services App IDを手動で挿入する必要があります。 アプリ ID を挿入する場所については、クライアントREADME.mdConfigurationの手順に従ってください。

2

Android Studio がプロジェクトをインデックス化している間に、プロジェクト組織を調べます。 app/java/com.mongodb.appディレクトリ内には、注目に値するファイルがいくつかあります。

ファイル
目的
ComposeItemActivity.kt
配置を定義し、Realm のオープン、Realm へのアイテムの書込み、ユーザーのログアウト、Realm を閉じるための機能を提供する アクティビティ クラス。
ComposLoginActivity.kt
レイアウトを定義し、ユーザーを登録してユーザーをログインするための機能を提供するアクティビティ クラス。
TemplateApp.kt
App Services Appを初期化するクラス。

このチュートリアルでは、次のファイルを操作します。

ファイル
目的
Item.kt
domainディレクトリにあります。 データベースに保存する Realm オブジェクトを定義します。
AddItem.kt
ui/tasksディレクトリにあります。 アイテムを追加するときに使用される配置を定義する 複合 関数が含まれています。
AddItemViewModel.kt
presentation/tasksディレクトリにあります。 ビジネス ロジックを含み、アイテムを追加するときに状態を管理するビュー モデル。
SyncRepository.kt
dataディレクトリにあります。 Realm Sync にアクセスするために使用され、Flexible Sync サブスクリプションを定義するリポジトリ。
String.xl
res/valuesディレクトリにあります。 テキスト string リソース を定義します アプリで使用されます。
3

コードを変更しなくても 、アプリは実行 できるはずです Android Studio を使用して Android エミュレータ、または物理デバイス上の。

アプリを実行し、新しいユーザーアカウントを登録し、新しいアイテムを Todo リストに追加します。

4

Atlas App Servicesにログインします。 Data Servicesタブで、 Browse Collectionsをクリックします。 データベースのリストで、 todoデータベース、 Itemコレクションを検索して展開します。 このコレクションで作成したドキュメントが表示されます。

1

すべて期待どおりに動作していることが確認できたため、変更を追加できます。 このチュートリアルでは、各アイテムに「優先順位」プロパティを追加し、アイテムに優先順位をつけてフィルタリングできるようにしました。 優先順位プロパティをPriorityLevel列挙型にマッピングして、可能な値を制限します。各列挙型の序数を使用して優先順位整数に対応するようにします。これにより、後で数値優先レベルに基づいてクエリを実行できるようになります。

これを行うには、次の手順に従います。

  1. app/java/com.mongodb.app/domainフォルダ内で、 Itemクラスファイルを開きます。

  2. 可能な値を制限するには、 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 {
    @PrimaryKey
    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
    }
2
  1. ui/tasksフォルダから、 AddItem.ktファイルを開きます。 このファイルは、ユーザーが [+] ボタンをクリックして新しい Todo 項目を追加するときに表示される UI の複合関数を定義します。

  2. まず、 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
  3. ここで、ユーザーが優先順位列挙型を使用可能な値として使用してリストから優先度レベルを選択できるようにするAddItemPromptにドロップダウン フィールドを追加します。

    ui/tasks/AddItem.kt
    // ... imports
    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    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 はいくつかのエラーを識別します。 次のステップでは、関連する関数を追加して、これらを修正します。

  4. 次に、ドロップダウン フィールド ラベルを string リソースとして定義します。 res/values/strings.xmlファイルを開き、「resource」要素を閉じる前に以下を追加します。

    res/values/strings.xl
    <string name="item_priority">Item Priority</string>
  5. 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
    }
  6. 最後に、 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
    }
3

この時点で、アプリケーションを再実行できます。 このチュートリアルの前半で作成したアカウントを使用してログインします。 過去に作成した 1 つのアイテムが表示されます。 新しいアイテムを追加すると、優先順位を設定できるようになります。 優先順位をHighに選択し、アイテムを保存します。

ブラウザで Atlas データページに切り替え、 Itemコレクションを更新します。 これでpriorityフィールドが追加され、 1に設定された新しいアイテムが表示されます。 既存のアイテムにはpriorityフィールドはありません。

コレクション内の 2 つのアイテム
クリックして拡大します

注意

同期が中断されなかった理由

Realm オブジェクトにプロパティを追加しても重大な変更ではないため、クライアントをリセットする必要はありません。 テンプレート アプリでは開発モードが有効になっているため、クライアント Realm オブジェクトへの変更はサーバー側のスキーマに反映されます。 詳しくは、「開発モードデータモデルの更新 」を参照してください。

1

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 ステートメントを編集します。

data/SyncRepository.kt
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に追加し、次にupdateExistingtrueに設定します。これにより、既存のクエリを更新できるようになります。

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()
2

アプリケーションを再度実行します。このチュートリアルの前半で作成したアカウントを使用してログインします。

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 オブジェクトにプロパティを追加することは重大じゃない変更であり、 開発モード によってスキーマの変更がサーバー側に反映されるようになります。

注意

フィードバックの共有

ではどのようにGoしましたか。 ページ右下にあるRate this pageウィジェットを使用して、有効性を評価します。 またはGithub リポジトリ に問題を報告する 問題が発生した場合は、。

次へ

Atlas アプリケーション サービスとは