Docs Menu
Docs Home
/ /
Atlas Device SDK
/ /

変更に対応する - React Kotlin SDK

項目一覧

  • クエリ変更リスナーの登録
  • RealmObject 変更リスナーの登録
  • コレクション変更リスナーの登録
  • キー パス変更リスナーの登録
  • ネストされたキー パスの監視
  • ワイルドカードによるキーパスの監視
  • 変更リスナーのサブスクライブを解除します
  • 通知制限の変更

最近のアプリは、データが変更されたときに、その変更がどこで行われたかにかかわらずReactを実行できるようになりました。 ユーザーが新しい項目をリストに追加するときに、UI を更新したり、通知を表示したり、メッセージをログに記録したりすることができます。 そのアイテムがアップデートされたら、その可視化状態を変更したり、ネットワークリクエストを起動したりする必要があるかもしれません。 最後に、誰かがアイテムを削除した場合は、UI からそれを削除する必要がある可能性があります。 Realmの通知システムを使用すると、変更の原因となった書込み (write) とはReactに、データ内の変更を監視し、対応することができます。

Kotlin SDK の固定アーキテクチャでは、通知の重要性がさらに高まります。 Kotlin SDK には自動的に更新されるライブ オブジェクトがないため、通知を使用して UI とデータ レイヤーを同期させます。

次のイベントの変更をサブスクライブできます。

  • コレクションに対するクエリ

  • Realm オブジェクト

  • Realm コレクション(例: リスト)

SDK は、最大 4 レイヤーの深度にネストされたオブジェクトの通知のみを提供します。 より深くReactされたオブジェクトの変更に対応する必要がある場合は、 キー パス変更リスナー を登録します。 詳細については、このページの「 キー パス変更リスナーの登録」を参照してください。

ユーザー認証状態の変更にReactすることもできます。 詳細については、「認証変更の監視 」を参照してください。

このページの例について

このページの例では、2 つの Realm オブジェクトタイプ、 CharacterFellowshipを使用します。

class Character(): RealmObject {
@PrimaryKey
var name: String = ""
var species: String = ""
var age: Int = 0
constructor(name: String, species: String, age: Int) : this() {
this.name = name
this.species = species
this.age = age
}
}
class Fellowship() : RealmObject {
@PrimaryKey
var name: String = ""
var members: RealmList<Character> = realmListOf()
constructor(name: String, members: RealmList<Character>) : this() {
this.name = name
this.members = members
}
}

例には、次のサンプル データがあります。

val config = RealmConfiguration.Builder(setOf(Fellowship::class, Character::class))
.name(realmName)
.build()
val realm = Realm.open(config)
val frodo = Character("Frodo", "Hobbit", 51)
val samwise = Character("Samwise", "Hobbit", 39)
val aragorn = Character("Aragorn", "Dúnedain", 87)
val legolas = Character("Legolas", "Elf", 2931)
val gimli = Character("Gimli", "Dwarf", 140)
val gollum = Character("Gollum", "Hobbit", 589)
val fellowshipOfTheRing = Fellowship(
"Fellowship of the Ring",
realmListOf(frodo, samwise, aragorn, legolas, gimli))
realm.writeBlocking{
this.copyToRealm(fellowshipOfTheRing)
this.copyToRealm(gollum) // not in fellowship
}
realm.close()

Realm 内の任意のクエリに通知ハンドラーを登録できます。 まず、 asFlow()を使用してクエリから Kotlin Flow を作成します。 次に、 collect()メソッドを使用して、そのフローに関するイベントを処理します。 タイプUpdatedResultsのイベントは、次のプロパティを使用してクエリに一致するオブジェクトに対するすべての変更を記録します。

プロパティ
タイプ
説明
insertions
IntArray
このバージョンで追加された新しいコレクション内のインデックス。
insertionRanges
配列未満<ListChangeSet.Range>
このバージョンで追加された新しいコレクション内のインデックスの範囲。
changes
IntArray
このバージョンで変更された新しいコレクション内のオブジェクトのインデックス。
changeRanges
配列未満<ListChangeSet.Range>
このバージョンで変更された新しいコレクション内のインデックスの範囲。
deletions
IntArray
このバージョンから削除された、コレクションの以前のバージョン内のインデックス。
deletionRanges
配列未満<ListChangeSet.Range>
このバージョンから削除された、コレクションの以前のバージョン内のインデックスの範囲。
list
RealmResults< RealmObject としての T<T as RealmObject>
変更が監視されている結果コレクション。
// Listen for changes on whole collection
val characters = realm.query(Character::class)
// flow.collect() is blocking -- run it in a background context
val job = CoroutineScope(Dispatchers.Default).launch {
// create a Flow from that collection, then add a listener to the Flow
val charactersFlow = characters.asFlow()
val subscription = charactersFlow.collect { changes: ResultsChange<Character> ->
when (changes) {
// UpdatedResults means this change represents an update/insert/delete operation
is UpdatedResults -> {
changes.insertions // indexes of inserted objects
changes.insertionRanges // ranges of inserted objects
changes.changes // indexes of modified objects
changes.changeRanges // ranges of modified objects
changes.deletions // indexes of deleted objects
changes.deletionRanges // ranges of deleted objects
changes.list // the full collection of objects
}
else -> {
// types other than UpdatedResults are not changes -- ignore them
}
}
}
}
// Listen for changes on RealmResults
val hobbits = realm.query(Character::class, "species == 'Hobbit'")
val hobbitJob = CoroutineScope(Dispatchers.Default).launch {
val hobbitsFlow = hobbits.asFlow()
val hobbitsSubscription = hobbitsFlow.collect { changes: ResultsChange<Character> ->
// ... all the same data as above
}
}

Realm 内の特定のオブジェクトに通知ハンドラーを登録できます。 Realm は、オブジェクトのプロパティのいずれかが変更されたときにハンドラーに通知します。 単一オブジェクトの変更リスナーを登録するには、 realm.query.first()を使用してRealmDoubleQueryを取得します。 asFlow()を使用して、そのクエリからフローを生成します。 ハンドラーは、次のサブタイプを使用してオブジェクトの変更を伝達するSingleQueryChangeオブジェクトを受け取ります。

サブタイプ
プロパティ
ノート
UpdatedObject
ChangedFieldsobj
フィールド名をisFieldChanged()に渡して、そのフィールドが変更されたかどうかを確認します。
DeletedObject
obj
objectは常にオブジェクトの最新バージョンを反映するため、このサブタイプでは常に null 値が返されます。
// query for the specific object you intend to listen to
val frodo = realm.query(Character::class, "name == 'Frodo'").first()
// flow.collect() is blocking -- run it in a background context
val job = CoroutineScope(Dispatchers.Default).launch {
val frodoFlow = frodo.asFlow()
frodoFlow.collect { changes: SingleQueryChange<Character> ->
when (changes) {
is UpdatedObject -> {
changes.changedFields // the changed properties
changes.obj // the object in its newest state
changes.isFieldChanged("name") // check if a specific field changed in value
}
is DeletedObject -> {
// if the object has been deleted
changes.obj // returns null for deleted objects -- always reflects newest state
}
is InitialObject -> {
// Initial event observed on a RealmObject or EmbeddedRealmObject flow.
// It contains a reference to the starting object state.
changes.obj
}
is PendingObject -> {
// Describes the initial state where a query result does not contain any elements.
changes.obj
}
}
}
}

通知ハンドラーは、 RealmListRealmSet 、またはRealmMapで登録できます。 Realm は、コレクション項目のいずれかが変更されると、ハンドラーに通知します。 まず、 asFlow()を使用して コレクションから Kotlin フローを作成します。 次に、 collect()メソッドを使用して、そのフローに関するイベントを処理します。 ListChangeSetChange 、またはMapChangeタイプのイベントは、コレクションに対するすべての変更を記録します。

RealmList 通知にはListchangeインターフェースが実装されています。これにより、RealmList コレクションで発生する可能性のある変更を記述します。 これらの状態はサブクラスによって表されます。

  • InitialList

  • UpdatedList

  • DeletedList

ListchangeSetは、リスト内で発生する可能性のある変更をモデル化するインターフェースであり、プロパティには次のものが含まれます。

プロパティ
タイプ
説明
insertions
IntArray
このバージョンで追加された新しいコレクション内のインデックス。
insertionRanges
配列未満<ListChangeSet.Range>
このバージョンで追加された新しいコレクション内のインデックスの範囲。
changes
IntArray
このバージョンで変更された新しいコレクション内のオブジェクトのインデックス。
changeRanges
配列未満<ListChangeSet.Range>
このバージョンで変更された新しいコレクション内のインデックスの範囲。
deletions
IntArray
このバージョンから削除された、コレクションの以前のバージョン内のインデックス。
deletionRanges
配列未満<ListChangeSet.Range>
このバージョンから削除された、コレクションの以前のバージョン内のインデックスの範囲。
list
RealmResults< RealmObject としての T<T as RealmObject>
変更が監視されている結果コレクション。 これは、イベントタイプがUpdatedListの場合に提供されます。

RealmSet 通知にはSetchangeインターフェースが実装されており、RealmSet コレクションで発生する可能性のある変更を記述します。 これらの状態はサブクラスによって表されます。

  • InitialSet

  • UpdatedSet

  • DeletedSet

SetchangeSetは、セット内で発生する可能性のある変更をモデル化するインターフェースであり、プロパティには次のものが含まれます。

プロパティ
タイプ
説明
deletions
Int
このバージョンの コレクションで削除されたエントリの数。
insertions
Int
このバージョンの コレクションに挿入されたエントリの数。
set
RealmSet<T>
変更が監視されているセット。 これは、イベントタイプがUpdatedSetの場合に提供されます。

RealmMap 通知には、 RealmMap コレクションで発生する可能性のある変更を記述するMapCheckインターフェースが実装されています。 これらの状態はサブクラスによって表されます。

  • InitialMap

  • UpdatedMap

  • DeletedMap

Map ChangeSetは、マップで発生する可能性のある変更をモデル化するインターフェースであり、プロパティには次のものが含まれます。

プロパティ
タイプ
説明
deletions
Array[K]<K>
このバージョンのマップで削除されたキー。
insertions
Array[K]<K>
このバージョンのマップに挿入されたキー。
changes
Array[K]<K>
このバージョンのコレクションで値が変更されたキー。
set
RealmMap<K,V>
変更が監視されているマップ。 これは、イベントタイプがUpdatedMapの場合に提供されます。
// query for the specific object you intend to listen to
val fellowshipOfTheRing = realm.query(Fellowship::class, "name == 'Fellowship of the Ring'").first().find()!!
val members = fellowshipOfTheRing.members
// flow.collect() is blocking -- run it in a background context
val job = CoroutineScope(Dispatchers.Default).launch {
val membersFlow = members.asFlow()
membersFlow.collect { changes: ListChange<Character> ->
when (changes) {
is UpdatedList -> {
changes.insertions // indexes of inserted objects
changes.insertionRanges // ranges of inserted objects
changes.changes // indexes of modified objects
changes.changeRanges // ranges of modified objects
changes.deletions // indexes of deleted objects
changes.deletionRanges // ranges of deleted objects
changes.list // the full collection of objects
}
is DeletedList -> {
// if the list was deleted
}
is InitialList -> {
// Initial event observed on a RealmList flow. It contains a reference
// to the starting list state.
changes.list
}
}
}
}

バージョン 1.13.0 の新機能

通知ハンドラーを登録するときに、string プロパティ名のオプションのリストを渡して、監視するキー パスを指定できます。

キーパスを指定すると、それらのキーパスに対する変更のみによって通知ブロックがtriggerされます。 その他の変更では通知ブロックはtriggerされません。

次の例では、 ageプロパティのキー パス変更リスナーを登録しています。

runBlocking {
// Query for the specific object you intend to listen to.
val frodoQuery = realm.query(Character::class, "name == 'Frodo'").first()
val observer = async {
val frodoFlow = frodoQuery.asFlow(listOf("age"))
frodoFlow.collect { changes: SingleQueryChange<Character> ->
// Change listener stuff in here.
}
}
// Changing a property whose key path you're not observing does not trigger a notification.
realm.writeBlocking {
findLatest(frodoObject)!!.species = "Ring Bearer"
}
// Changing a property whose key path you are observing triggers a notification.
realm.writeBlocking {
findLatest(frodoObject)!!.age = 52
}
// For this example, we send the object change to a Channel where we can verify the
// changes we expect. In your application code, you might use the notification to
// update the UI or take some other action based on your business logic.
channel.receiveOrFail().let { objChange ->
assertIs<UpdatedObject<*>>(objChange)
assertEquals(1, objChange.changedFields.size)
// Because we are observing only the `age` property, the change to
// the `species` property does not trigger a notification.
// The first notification we receive is a change to the `age` property.
assertEquals("age", objChange.changedFields.first())
}
observer.cancel()
channel.close()
}

注意

個別のキー パスでフィルタリングされる同じオブジェクト上の複数の通知トークンは、排他的にフィルタリングされません。 1 つの通知トークンに対して 1 つのキーパスの変更が満たされた場合、そのオブジェクトのすべての通知トークン ブロックが実行されます。

ドット表記 を使用して、ネストされたキー パスを確認できます。 デフォルトでは、SDK は最大 4 レイヤーにネストされたオブジェクトの通知のみを報告します。 より深くネストされたオブジェクトの変更を監視する必要がある場合は、特定のキー パスを監視します。

次の例では、ネストされたプロパティmembers.ageの更新を監視しています。 ネストされたプロパティに対する変更を監視しているにもかかわらず、SDK は最上位のmembersプロパティへの変更を報告していることに注意してください。

runBlocking {
// Query for the specific object you intend to listen to.
val fellowshipQuery = realm.query(Fellowship::class).first()
val observer = async {
val fellowshipFlow = fellowshipQuery.asFlow(listOf("members.age"))
fellowshipFlow.collect { changes: SingleQueryChange<Fellowship> ->
// Change listener stuff in here.
}
}
// Changing a property whose nested key path you are observing triggers a notification.
val fellowship = fellowshipQuery.find()!!
realm.writeBlocking {
findLatest(fellowship)!!.members[0].age = 52
}
// For this example, we send the object change to a Channel where we can verify the
// changes we expect. In your application code, you might use the notification to
// update the UI or take some other action based on your business logic.
channel.receiveOrFail().let { objChange ->
assertIs<UpdatedObject<*>>(objChange)
assertEquals(1, objChange.changedFields.size)
// While you can watch for updates to a nested property, the notification
// only reports the change on the top-level property. In this case, there
// was a change to one of the elements in the `members` property, so `members`
// is what the notification reports - not `age`.
assertEquals("members", objChange.changedFields.first())
}
observer.cancel()
channel.close()
}

ワイルドカード( * )を使用すると、ワイルドカードのレベルですべてのキーパスに対する変更を確認できます。

次の例では、ワイルドカード監視を使用して、 membersプロパティ内の 1 つのレベルのネストされたプロパティに対する変更を確認します。 この例で使用されているモデルに基づいて、この変更リスナーは任意のノードのagespecies 、またはnameプロパティの変更を報告します。 ネストされたプロパティに対する変更を監視しているにもかかわらず、SDK は最上位のmembersプロパティへの変更を報告していることに注意してください。

runBlocking {
// Query for the specific object you intend to listen to.
val fellowshipQuery = realm.query(Fellowship::class).first()
val observer = async {
// Use a wildcard to observe changes to any key path at the level of the wildcard.
val fellowshipFlow = fellowshipQuery.asFlow(listOf("members.*"))
fellowshipFlow.collect { changes: SingleQueryChange<Fellowship> ->
// Change listener stuff in here.
}
}
// Changing any property at the level of the key path wild card triggers a notification.
val fellowship = fellowshipQuery.find()!!
realm.writeBlocking {
findLatest(fellowship)!!.members[0].age = 52
}
// For this example, we send the object change to a Channel where we can verify the
// changes we expect. In your application code, you might use the notification to
// update the UI or take some other action based on your business logic.
channel.receiveOrFail().let { objChange ->
assertIs<UpdatedObject<*>>(objChange)
assertEquals(1, objChange.changedFields.size)
// While you can watch for updates to a nested property, the notification
// only reports the change on the top-level property. In this case, there
// was a change to one of the elements in the `members` property, so `members`
// is what the notification reports - not `age`.
assertEquals("members", objChange.changedFields.first())
}
observer.cancel()
channel.close()
}

監視対象のデータへのアップデートに関する通知を受信したくない場合は、変更リスナーからサブスクライブを解除します。変更リスナーのサブスクライブを解除 するには、囲みているコルーチンをキャンセルします。

// query for the specific object you intend to listen to
val fellowshipOfTheRing = realm.query(Fellowship::class, "name == 'Fellowship of the Ring'").first().find()!!
val members = fellowshipOfTheRing.members
// flow.collect() is blocking -- run it in a background context
val job = CoroutineScope(Dispatchers.Default).launch {
val membersFlow = members.asFlow()
membersFlow.collect { changes: ListChange<Character> ->
// change listener stuff in here
}
}
job.cancel() // cancel the coroutine containing the listener

4 レベルよりも深いネストされたドキュメントの変更では、変更通知はtriggerされません。

5 レベル以上の変更をリッスンする必要があるデータ構造がある場合は、次の回避策があります。

  • スキーマをリファクタリングしてネストを減らします。

  • ユーザーがデータを手動で更新できるようにするには、「push-to-refresh」のようなものを追加します。

戻る

Realm のバンドル