변경 사항에 대응 - 코틀린 SDK (Kotlin SDK)
이 페이지의 내용
모든 최신 앱은 변경 사항이 발생한 위치에 관계없이 데이터가 변경되면 React 수 있어야 합니다. 사용자가 목록에 새 항목을 추가할 때 UI를 업데이트하거나, 알림을 표시하거나, 메시지를 기록해야 할 수 있습니다. 누군가 해당 항목을 업데이트하면 해당 항목의 시각적 상태를 변경하거나 네트워크 요청을 실행해야 할 수 있습니다. 마지막으로, 누군가 항목을 삭제하면 UI에서 해당 항목을 제거해야 할 수 있습니다. Realm의 알림 시스템을 사용하면 변경을 일으킨 쓰기가 무엇이든 데이터의 변경 사항을 관찰하고 React 수 있습니다.
코틀린 SDK (Kotlin SDK)의 동결 아키텍처는 알림의 중요성을 더욱 강조합니다. 코틀린 SDK (Kotlin SDK)에는 자동으로 업데이트되는 라이브 객체가 없으므로 알림을 사용하여 UI와 데이터 영역을 동기화된 상태로 유지해야 합니다.
다음 이벤트에 대한 변경 사항을 구독할 수 있습니다.
SDK는 최대 4개의 레이어 깊이까지 중첩된 객체에 대해서만 알림을 제공합니다. 더 깊게 중첩된 객체의 변경 사항에 대응해야 하는 경우 주요 경로 변경 리스너를 등록하세요. 자세한 내용은 이 페이지 의 키 경로 변경 리스너 등록을 참조하세요.
사용자 인증 상태의 변경에 React 수도 있습니다. 자세한 내용은 인증 변경 관찰을 참조하세요.
예시
해당 페이지의 예제에 대한 정보
이 페이지의 예시에서는 Character
및 Fellowship
의 두 가지 Realm 객체 유형을 사용합니다.
class Character(): RealmObject { 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 { 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 | Array<ListChangeSet.Range> | 이 버전에 추가된 새 collection의 인덱스 범위입니다. |
changes | IntArray | 이 버전에서 수정된 새 컬렉션에 있는 객체의 인덱스입니다. |
changeRanges | Array<ListChangeSet.Range> | 이 버전에서 수정된 새 collection의 인덱스 범위입니다. |
deletions | IntArray | 이전 버전의 컬렉션에 있었지만 이 컬렉션에서 제거된 인덱스입니다. |
deletionRanges | Array<ListChangeSet.Range> | 이 컬렉션에서 제거된 이전 버전 컬렉션의 인덱스 범위입니다. |
list | RealmResults<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 } }
RealmObject 변경 리스너 등록
영역 내의 특정 객체 에 대한 알림 핸들러를 등록할 수 있습니다. Realm 은 객체의 속성이 변경되면 핸들러에게 알립니다. 단일 객체 에 변경 리스너를 등록하려면 realm.query.first()
을 사용하여 RealmSingleQuery 를 가져옵니다. asFlow() 를 사용하여 해당 쿼리 에서 흐름을 생성합니다. 핸들러는 다음 하위 유형을 사용하여 객체 변경 사항을 전달하는 SingleQueryChange
객체 를 수신합니다.
하위 유형 | 속성 | 참고 사항 |
UpdatedObject | changeFields, 객체 | 필드 이름을 isFieldChanged() 에 전달하여 해당 필드가 변경되었는지 확인합니다. |
DeletedObject | obj | obj 는 항상 최신 버전의 객체를 반영하므로 항상 이 하위 유형에서 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 } } } }
컬렉션 변경 리스너 등록
RealmList
, RealmSet
또는 RealmMap
에서 알림 핸들러를 등록할 수 있습니다. 컬렉션 항목이 변경되면 Realm에서 핸들러에게 알립니다. 먼저 asFlow()
을 사용하여 collection에서 Kotlin Flow를 만듭니다. 다음으로 collect()
메서드를 사용하여 해당 흐름에서 이벤트를 처리합니다. ListChange
, SetChange
또는 MapChange
유형의 Events는 collection의 모든 변경 사항을 기록합니다.
RealmList
알림 은 RealmList 컬렉션 에서 발생할 수 있는 변경 사항을 설명하는 ListChange 인터페이스를 구현 합니다. 이러한 상태는 하위 클래스로 표시됩니다.
InitialList
UpdatedList
DeletedList
ListChangeSet 는 목록에서 발생할 수 있는 변경 사항을 모델링하는 인터페이스이며, 속성에는 다음이 포함됩니다.
속성 | 유형 | 설명 |
insertions | IntArray | 이 버전에 추가된 새 컬렉션의 인덱스입니다. |
insertionRanges | Array<ListChangeSet.Range> | 이 버전에 추가된 새 collection의 인덱스 범위입니다. |
changes | IntArray | 이 버전에서 수정된 새 컬렉션에 있는 객체의 인덱스입니다. |
changeRanges | Array<ListChangeSet.Range> | 이 버전에서 수정된 새 collection의 인덱스 범위입니다. |
deletions | IntArray | 이전 버전의 컬렉션에 있었지만 이 컬렉션에서 제거된 인덱스입니다. |
deletionRanges | Array<ListChangeSet.Range> | 이 컬렉션에서 제거된 이전 버전 컬렉션의 인덱스 범위입니다. |
list | RealmResults<T as RealmObject> | 결과 collection에 변경 사항이 있는지 모니터링 중입니다. 이벤트 유형이 UpdatedList 인 경우 제공됩니다. |
RealmSet
알림 은 RealmSet 컬렉션 에서 발생할 수 있는 변경 사항을 설명하는 SetChange 인터페이스를 구현 합니다. 이러한 상태는 하위 클래스로 표시됩니다.
InitialSet
UpdatedSet
DeletedSet
SetChangeSet 는 설정하다 에서 발생할 수 있는 변경 사항을 모델링하는 인터페이스이며, 속성에는 다음이 포함됩니다.
속성 | 유형 | 설명 |
deletions | Int | 이 버전의 컬렉션에서 삭제된 항목 수입니다. |
insertions | Int | 컬렉션의 이 버전에 삽입된 항목 수입니다. |
set | RealmSet<T> | 변경 사항을 모니터링 중인 설정하다 입니다. 이벤트 유형이 UpdatedSet 인 경우 제공됩니다. |
RealmMap
알림 은 RealmMap 컬렉션 에서 발생할 수 있는 변경 사항을 설명하는 MapChange 인터페이스를 구현 합니다. 이러한 상태는 하위 클래스로 표시됩니다.
InitialMap
UpdatedMap
DeletedMap
MapChangeSet 는 지도에서 발생할 수 있는 변경 사항을 모델링하는 인터페이스이며, 속성에는 다음이 포함됩니다.
속성 | 유형 | 설명 |
deletions | Array<K> | 이 버전의 지도에서 삭제된 키입니다. |
insertions | Array<K> | 이 버전의 지도에 삽입된 키입니다. |
changes | Array<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에 추가 되었습니다.
알림 핸들러를 등록할 때 선택적 문자열 속성 이름 목록을 전달하여 관찰할 키 경로를 지정할 수 있습니다.
키 경로를 지정한 경우 해당 키 경로에 대한 변경만 알림 차단을 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() }
참고
별도의 키 경로를 필터링하는 동일한 객체의 여러 알림 토큰은 독점적으로 필터링 되지 않습니다 . 하나의 알림 토큰에 대해 하나의 키 경로 변경이 충족되면 해당 객체에 대한 모든 알림 토큰 차단이 실행됩니다.
중첩된 키 경로 관찰
점 표기법을 사용하여 중첩된 키 경로를 관찰할 수 있습니다. 기본적으로 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
속성 내 한 수준 깊이 있는 모든 중첩 속성에 대한 변경 사항에 와일드카드 감시를 사용합니다. 이 예제에서 사용된 모델을 기반으로 이 변경 리스너는 모든 멤버의 age
, species
또는 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단계 아래로 내려가면 변경 알림이 트리거되지 않습니다.
5단계 아래 또는 그 이상의 변경 사항을 수신해야 하는 데이터 구조가 있는 경우 해결 방법은 다음과 같습니다.
스키마를 리팩터링하여 중첩을 줄입니다.
사용자가 수동으로 데이터를 새로 고칠 수 있도록 '푸시하여 새로 고침'과 같은 내용을 추가합니다.