@amityco/social-plus-vise 0.14.28 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +6 -11
- package/dist/tools/ast.js +153 -28
- package/dist/tools/docs.js +48 -0
- package/dist/tools/sdkFacts.js +45 -1
- package/docs-cache/README.md +34 -0
- package/docs-cache/social-plus-sdk/core-concepts/foundation/logging.mdx +236 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/live-objects-collections/android.mdx +262 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/live-objects-collections/flutter.mdx +195 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/live-objects-collections/ios.mdx +452 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/live-objects-collections/overview.mdx +133 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/live-objects-collections/typescript.mdx +264 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/push-notifications/register-and-unregister-push-notifications-on-a-device.mdx +191 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/push-notifications/settings/overview.mdx +43 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/push-notifications/setup/android-setup.mdx +360 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/push-notifications/setup/flutter-setup.mdx +457 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/push-notifications/setup/ios-setup.mdx +423 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/push-notifications/setup/react-native-setup.mdx +384 -0
- package/docs-cache/social-plus-sdk/core-concepts/realtime-communication/realtime-events/overview.mdx +94 -0
- package/docs-cache/social-plus-sdk/getting-started/authentication.mdx +808 -0
- package/docs-cache/social-plus-sdk/getting-started/platform-setup/mobile/android-quick-start.mdx +304 -0
- package/docs-cache/social-plus-sdk/getting-started/platform-setup/mobile/flutter-quick-start.mdx +121 -0
- package/docs-cache/social-plus-sdk/getting-started/platform-setup/mobile/ios-quick-start.mdx +225 -0
- package/docs-cache/social-plus-sdk/getting-started/platform-setup/web/web-quick-start.mdx +99 -0
- package/docs-cache/social-plus-sdk/social/communities-spaces/organization/community-invitation.mdx +459 -0
- package/docs-cache/social-plus-sdk/social/communities-spaces/organization/join-leave-community.mdx +449 -0
- package/docs-cache/social-plus-sdk/social/communities-spaces/organization/query-community-members.mdx +376 -0
- package/docs-cache/social-plus-sdk/social/content-management/posts/creation/text-post.mdx +318 -0
- package/docs-cache/social-plus-sdk/social/discovery-engagement/notifications/notification-tray-status.mdx +399 -0
- package/docs-cache/social-plus-sdk/social/user-relationship/blocking/block-unblock-user.mdx +166 -0
- package/docs-cache/social-plus-sdk/social/user-relationship/following/get-follower-following-list.mdx +339 -0
- package/package.json +10 -3
- package/scripts/dart-model-extractor/bin/extract_models.dart +169 -0
- package/scripts/dart-model-extractor/pubspec.lock +149 -0
- package/scripts/dart-model-extractor/pubspec.yaml +16 -0
- package/scripts/extract-sdk-models.mjs +353 -12
- package/scripts/import-sdk-surface.mjs +10 -19
- package/sdk-surface/manifest.json +15 -15
- package/sdk-surface/models.android.json +1 -1
- package/sdk-surface/models.flutter.json +465 -465
- package/sdk-surface/models.ios.json +188 -188
- package/sdk-surface/models.typescript.json +1 -1
- package/skills/social-plus-vise/SKILL.md +14 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# iOS Live Objects/Collections
|
|
2
|
+
|
|
3
|
+
> Live Objects are supported in the iOS SDK with AmityObject and AmityCollection for real-time data synchronization
|
|
4
|
+
|
|
5
|
+
In iOS social.plus SDK, we have two core concepts for real-time data synchronization: **Live Object** and **Live Collection**.
|
|
6
|
+
|
|
7
|
+
Live Object is represented by `AmityObject` instance for observing changes in a single object whereas Live Collection is represented by `AmityCollection` instance for observing changes in a list of objects.
|
|
8
|
+
|
|
9
|
+
These generic classes encapsulate any other object and notify observers whenever any property of the encapsulated object changes.
|
|
10
|
+
|
|
11
|
+
Examples: `AmityObject<AmityPost>`, `AmityCollection<AmityMessage>`, or `AmityObject<AmityChannel>`.
|
|
12
|
+
|
|
13
|
+
## How it Works
|
|
14
|
+
|
|
15
|
+
SDK handles lots of data received from various sources. Data can be present in local cache. It might also be queried from the server or received from some real-time events. What this means is that same data is constantly updating. The data that you are accessing at the moment can get updated by other sources and becomes out of sync.
|
|
16
|
+
|
|
17
|
+
Live Object and Live Collection help in syncing these constantly updating data, so you will always get the most recent one. Whenever the data gets updated, you will be notified through helper methods in live objects and live collection classes.
|
|
18
|
+
|
|
19
|
+
New data gets automatically collected every time when there is an updation and the user need not refresh to get the recent data.
|
|
20
|
+
|
|
21
|
+
### Data Sources
|
|
22
|
+
|
|
23
|
+
Data present in local storage
|
|
24
|
+
Data queried from the server
|
|
25
|
+
Data received from real-time events
|
|
26
|
+
|
|
27
|
+
Both `AmityObject` and `AmityCollection` provide methods for observing changes in objects. The life cycle of observation is tied to its token. As soon as the token is invalidated or deallocated, observation ends.
|
|
28
|
+
|
|
29
|
+
## AmityNotificationToken
|
|
30
|
+
|
|
31
|
+
`AmityNotificationToken` is a simple object which keeps track of what is being observed. Each Live Object or Live Collection observation is tied to its respective token. As soon as the token is invalidated or deallocated, observation ends. The token is declared within the scope of the class.
|
|
32
|
+
|
|
33
|
+
```swift
|
|
34
|
+
class MyClass {
|
|
35
|
+
var token: AmityNotificationToken?
|
|
36
|
+
|
|
37
|
+
func doSomething() {
|
|
38
|
+
// AmityNotification is alive in MyClass scope
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The token is used in combination with AmityObject or AmityCollection. We will explore it more in AmityObject and AmityCollection concepts.
|
|
44
|
+
|
|
45
|
+
## AmityObject
|
|
46
|
+
|
|
47
|
+
`AmityObject` is a generic class that keeps track of a single object. It is a live object. In iOS AmitySDK, any object which is encapsulated by AmityObject is a live object.
|
|
48
|
+
|
|
49
|
+
Examples: `AmityObject<AmityMessage>`, `AmityObject<AmityChannel>`
|
|
50
|
+
|
|
51
|
+
AmityObject class exposes the following methods: `observe` and `observeOnce`. These methods help observe a live object. Whenever any property for the observed object changes, the observer is triggered.
|
|
52
|
+
|
|
53
|
+
Please make sure that the user is logged in before observing AmityObject. You can also refer to the session state section for more details.
|
|
54
|
+
|
|
55
|
+
### Observer
|
|
56
|
+
|
|
57
|
+
`observe` method can be triggered multiple times throughout the lifetime of the application as long as its associated AmityNotificationToken is retained in memory. `observeOnce` method, on the other hand, can only be triggered once.
|
|
58
|
+
|
|
59
|
+
Both `observe` and `observeOnce` methods will be called from the main thread so you can perform any UI update-related tasks from within the observed block itself.
|
|
60
|
+
|
|
61
|
+
If the requested object data is stored locally on the device, the block will be called immediately with the local version of the data. This can be verified through the dataStatus property of AmityObject.
|
|
62
|
+
|
|
63
|
+
In parallel, a request is made to the server to fetch the latest version of the data. Once the data is returned, the observed block will be triggered again.
|
|
64
|
+
|
|
65
|
+
Any future changes to that data from any sources can trigger an observer.
|
|
66
|
+
|
|
67
|
+
**Lifecycle**: The life cycle of the observer is tied to its token. If the token is not retained, then the observer can get deallocated at any time and will not be called.
|
|
68
|
+
|
|
69
|
+
```swift
|
|
70
|
+
var token1: AmityNotificationToken?
|
|
71
|
+
var token2: AmityNotificationToken?
|
|
72
|
+
|
|
73
|
+
func example() {
|
|
74
|
+
let liveObject: AmityObject<AmityChannel> = channelRepository.getChannel("1234")
|
|
75
|
+
|
|
76
|
+
// observe block
|
|
77
|
+
token1 = liveObject.observe { liveObject, error in
|
|
78
|
+
// Handle updates
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// observeOnce block
|
|
82
|
+
token2 = liveObject.observeOnce { liveObject, error in
|
|
83
|
+
// Handle single update
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```swift
|
|
89
|
+
// Manually invalidate the token tied to observer
|
|
90
|
+
token1?.invalidate()
|
|
91
|
+
|
|
92
|
+
// Alternatively, you can invalidate a token by deallocate it
|
|
93
|
+
token1 = nil
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Accessing Objects
|
|
97
|
+
|
|
98
|
+
There are multiple ways to access data from AmityObject. AmityObject exposes the following properties:
|
|
99
|
+
|
|
100
|
+
- **dataStatus**: Indicates whether the data is fresh or local
|
|
101
|
+
- **loadingStatus**: Indicates if the data is being loaded from server or not
|
|
102
|
+
- **object**: The actual object that is being tracked or encapsulated by this AmityObject
|
|
103
|
+
|
|
104
|
+
Once you add an observer block, you can access both local or fresh data as shown below.
|
|
105
|
+
|
|
106
|
+
```swift
|
|
107
|
+
var token: AmityNotificationToken?
|
|
108
|
+
|
|
109
|
+
func example() {
|
|
110
|
+
let liveObject: AmityObject<AmityChannel> = channelRepository.getChannel("1234")
|
|
111
|
+
|
|
112
|
+
token = liveObject.observe { liveObject, error in
|
|
113
|
+
// This helps us to determine if the data is fresh or local
|
|
114
|
+
let dataStatus: AmityDataStatus = liveObject.dataStatus
|
|
115
|
+
|
|
116
|
+
// Here we access channel from this live object. This gives us with `AmityChannel` object.
|
|
117
|
+
guard let channel = liveObject.object else {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Access channel object property as normal
|
|
122
|
+
let channelId = channel.channelId
|
|
123
|
+
let channelDisplayName = channel.displayName
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```swift
|
|
129
|
+
var token: AmityNotificationToken?
|
|
130
|
+
|
|
131
|
+
func example() {
|
|
132
|
+
let liveObject = channelRepository.getChannel("1234")
|
|
133
|
+
|
|
134
|
+
token = liveObject.observe { [weak self] liveObject, error in
|
|
135
|
+
// We want to get fresh data only.
|
|
136
|
+
guard liveObject.dataStatus == .fresh else {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
guard let channel = liveObject.object else {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Access channel object property as normal
|
|
145
|
+
let channelId = channel.channelId
|
|
146
|
+
let channelDisplayName = channel.displayName
|
|
147
|
+
|
|
148
|
+
// We don't want to observe future changes. So we stop observing by invaliding token
|
|
149
|
+
self?.token?.invalidate()
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```swift
|
|
155
|
+
let liveObject: AmityObject<AmityChannel> = channelRepository.getChannel("1234")
|
|
156
|
+
|
|
157
|
+
// If local data is present, you can also access it as...
|
|
158
|
+
if let channel = liveObject.object {
|
|
159
|
+
// Access channel properties
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
While this is possible, we recommend accessing object from within the `observe` or `observeOnce` block depending on your requirement.
|
|
164
|
+
|
|
165
|
+
For `observeOnce` method, if data is present locally, this observer will be triggered only once with that local data. If you are looking for fresh data, use the `observe` block and invalidate the token once fresh data is received as shown above.
|
|
166
|
+
|
|
167
|
+
### AmityLoadingStatus
|
|
168
|
+
|
|
169
|
+
AmityObject can be tracked for their loading status by accessing the `loadingStatus` property, which is of type `AmityLoadingStatus` and it can have one of four possible values.
|
|
170
|
+
|
|
171
|
+
- **0 (notLoading)**: Indicates that the data is already fresh locally and does not need to be loaded.
|
|
172
|
+
- **1 (loading)**: Indicates that the client is currently loading the data from the server.
|
|
173
|
+
- **2 (loaded)**: Indicates that the client has successfully loaded fresh data from the server and is up to date.
|
|
174
|
+
- **3 (error)**: Indicates that the data is unable to load due from a specific error.
|
|
175
|
+
|
|
176
|
+
## AmityCollection
|
|
177
|
+
|
|
178
|
+
`AmityCollection` is a generic class that keeps track of a collection of objects. It is a live collection. In iOS SDK, any object which is encapsulated by AmityCollection class is a live collection.
|
|
179
|
+
|
|
180
|
+
Examples: `AmityCollection<AmityMessage>`, `AmityCollection<AmityChannel>`
|
|
181
|
+
|
|
182
|
+
AmityCollection exposes these methods: `observe` and `observeOnce`. These methods help to observe a live collection. Whenever any property for any object within the collection changes, the observer is triggered.
|
|
183
|
+
|
|
184
|
+
Please make sure that the user is logged in before observing AmityCollection. You can also refer to session state section for more details.
|
|
185
|
+
|
|
186
|
+
### Observer
|
|
187
|
+
|
|
188
|
+
`observe` method can get triggered multiple times throughout the lifetime of the application as long as it's associated AmityNotificationToken is retained in memory. `observeOnce`, on the other hand, can only be triggered once.
|
|
189
|
+
|
|
190
|
+
Both `observe` and `observeOnce` method will be called from the main thread so you can perform any UI update related task within the observe block itself.
|
|
191
|
+
|
|
192
|
+
If the requested data collection is stored locally on the device, the block will be called immediately with the local version of the data. This can be verified through the dataStatus property of AmityCollection.
|
|
193
|
+
|
|
194
|
+
In parallel, a request is made to the server to fetch the latest version of the data. Once the data is returned, the observe block will be triggered again.
|
|
195
|
+
|
|
196
|
+
Any future changes to the data from any sources can trigger observer.
|
|
197
|
+
|
|
198
|
+
**Lifecycle**: The life cycle of the observer for AmityCollection is also tied to its token. If the token is not retained, the observer can get deallocated at any time and will not be called.
|
|
199
|
+
|
|
200
|
+
### Accessing Collection
|
|
201
|
+
|
|
202
|
+
Unlike most databases, AmityCollection does not return all data in an array. Instead, data is fetched one by one using the `objectAtIndex:` method. This allows the framework to store most of the actual result on disk, and load them in memory only when necessary.
|
|
203
|
+
|
|
204
|
+
AmityCollection also exposes a `count` property which determines the number of objects present in a collection.
|
|
205
|
+
|
|
206
|
+
With these two public interfaces, you can create a robust list UI for your use case. Similar to AmityObject, AmityCollection also exposes `dataStatus` and `loadingStatus` property.
|
|
207
|
+
|
|
208
|
+
```swift
|
|
209
|
+
var token: AmityNotificationToken?
|
|
210
|
+
|
|
211
|
+
func example() {
|
|
212
|
+
// Fetching live collection of channels...
|
|
213
|
+
let query = AmityChannelQuery()
|
|
214
|
+
query.types = [AmityChannelQueryType.live]
|
|
215
|
+
query.includeDeleted = false
|
|
216
|
+
let liveCollection = channelRepository.getChannels(with: query)
|
|
217
|
+
|
|
218
|
+
// observe block
|
|
219
|
+
token = liveCollection.observe { liveCollection, change, error in
|
|
220
|
+
// Determine if data is local or fresh
|
|
221
|
+
let dataStatus: AmityDataStatus = liveCollection.dataStatus
|
|
222
|
+
|
|
223
|
+
// Using the count property we loop through collection data
|
|
224
|
+
for i in 0..<liveCollection.count() {
|
|
225
|
+
// We access individual data
|
|
226
|
+
if let channel = liveCollection.object(at: i) {
|
|
227
|
+
// Access channel object property as normal object i.e AmityChannel
|
|
228
|
+
let channelId = channel.channelId
|
|
229
|
+
let channelDisplayName = channel.displayName
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
```swift
|
|
237
|
+
// AmityCollection returns a maximum of 20 items per page
|
|
238
|
+
// For example, when user scrolls to the bottom of the list; fetch next page
|
|
239
|
+
if liveCollection.hasNext {
|
|
240
|
+
liveCollection.nextPage()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// If you want to shrink the collection to the original first page
|
|
244
|
+
liveCollection.resetPage()
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
AmityCollection has `nextPage()` and `previousPage()` method to fetch more data. It also exposes `hasNext` and `hasPrevious` property to check if next page or previous page is present.
|
|
248
|
+
|
|
249
|
+
One typical usage of Live Collection is in UITableView. Below is an example of fetching a collection and displaying it in a tableview.
|
|
250
|
+
|
|
251
|
+
```swift
|
|
252
|
+
class ExampleViewController: UITableViewController {
|
|
253
|
+
|
|
254
|
+
var liveCollection: AmityCollection<AmityChannel>!
|
|
255
|
+
var token: AmityNotificationToken?
|
|
256
|
+
|
|
257
|
+
override func viewDidLoad() {
|
|
258
|
+
super.viewDidLoad()
|
|
259
|
+
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
|
|
260
|
+
setupDataSource()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
func setupDataSource() {
|
|
264
|
+
let query = AmityChannelQuery()
|
|
265
|
+
query.types = [AmityChannelQueryType.live]
|
|
266
|
+
query.includeDeleted = false
|
|
267
|
+
// Get live collection
|
|
268
|
+
liveCollection = App.shared.channelRepository.getChannels(with: query)
|
|
269
|
+
// Observe live collection
|
|
270
|
+
token = liveCollection.observe { [weak self] liveCollection, changes, error in
|
|
271
|
+
self?.tableView.reloadData()
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
override func numberOfSections(in tableView: UITableView) -> Int {
|
|
276
|
+
1
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
280
|
+
Int(liveCollection.count())
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
284
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
|
|
285
|
+
// build your cell here with AmityChannel object.
|
|
286
|
+
if let channel = liveCollection.object(at: indexPath.row) {
|
|
287
|
+
cell.textLabel?.text = channel.displayName
|
|
288
|
+
}
|
|
289
|
+
return cell
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## SwiftUI Support
|
|
295
|
+
|
|
296
|
+
AmityObject and AmityCollection are now observable object with its properties marked with @Published annotation. Now you can use live object & live collection directly within your SwiftUI views.
|
|
297
|
+
|
|
298
|
+
**Live Object @Published**: dataStatus ✅, loadingStatus ✅, snapshot ✅, error ✅, object ❌
|
|
299
|
+
|
|
300
|
+
**Live Collection @Published**: dataStatus ✅, loadingStatus ✅, snapshot ✅, error ✅
|
|
301
|
+
|
|
302
|
+
### Access AmityObject & AmityCollection in SwiftUI views
|
|
303
|
+
|
|
304
|
+
Since AmityObject & AmityCollection are observable object, it can be used as an ObservedObject within SwiftUI views. We recommend to create small view which observes AmityCollection & AmityObject as ObservedObject as shown in code sample below.
|
|
305
|
+
|
|
306
|
+
```swift
|
|
307
|
+
struct LiveCommunityView: View {
|
|
308
|
+
@ObservedObject var liveObject: AmityObject<AmityCommunity>
|
|
309
|
+
|
|
310
|
+
var body: some View {
|
|
311
|
+
VStack(alignment: .leading) {
|
|
312
|
+
Text(liveObject.snapshot?.displayName ?? "")
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```swift
|
|
319
|
+
struct CommunityListView: View {
|
|
320
|
+
@ObservedObject var communityCollection: AmityCollection<AmityCommunity>
|
|
321
|
+
|
|
322
|
+
var body: some View {
|
|
323
|
+
List(communityCollection.snapshots, id: \.communityId) { item in
|
|
324
|
+
CommunityRowItemView(community: item)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
```swift
|
|
331
|
+
func observeChangesInLiveObject() {
|
|
332
|
+
var cancellable: AnyCancellable?
|
|
333
|
+
|
|
334
|
+
var liveObject: AmityObject<AmityCommunity> = communityRepository.getCommunity(withId: "abcd")
|
|
335
|
+
cancellable = liveObject.$snapshot.sink { community in
|
|
336
|
+
// Observe changes to AmityCommunity
|
|
337
|
+
let displayName = community?.displayName ?? "Unknown"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```swift
|
|
343
|
+
func observeChangesInLiveCollection() {
|
|
344
|
+
var cancellable1: AnyCancellable?
|
|
345
|
+
var cancellable2: AnyCancellable?
|
|
346
|
+
|
|
347
|
+
var liveCollection: AmityCollection<AmityCommunity> = communityRepository.getTrendingCommunities()
|
|
348
|
+
cancellable1 = liveCollection.$snapshots.sink { communities in
|
|
349
|
+
// Observe changes to [AmityCommunity]
|
|
350
|
+
let communitiesCount = communities.count
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
cancellable2 = liveCollection.$error.sink { communities in
|
|
354
|
+
// Some error occurred
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Since the properties are published, If you are using Combine Framework, you can also subscribe to changes on those properties.
|
|
360
|
+
|
|
361
|
+
States of live collection & live object are published before snapshots so that you can compare the state from within subscriber.
|
|
362
|
+
|
|
363
|
+
## FAQ's
|
|
364
|
+
|
|
365
|
+
AmityCollection & AmityObject is an ObservableObject. When this live collection or live object is embedded inside another Observable Object, SwiftUI cannot observe the changes in snapshot occurring within Live collection & Live object. As a result, there might be a situation where you see your view is not getting updated even when snapshot(s) are updated. This is a common problem with nested observable object in SwiftUI.
|
|
366
|
+
|
|
367
|
+
**❌ Error: View where list does not update**
|
|
368
|
+
```swift
|
|
369
|
+
class MyViewModel: ObservableObject {
|
|
370
|
+
var commentRepo: AmityCommentRepository!
|
|
371
|
+
@Published var liveCollection: AmityCollection<AmityComment>!
|
|
372
|
+
|
|
373
|
+
init() {
|
|
374
|
+
let commentQuery = AmityCommentQueryOptions(referenceId: "", referenceType: .post, filterByParentId: false, orderBy: .descending, includeDeleted: false)
|
|
375
|
+
liveCollection = commentRepo.getComments(with: commentQuery)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
struct MyView1: View {
|
|
380
|
+
@StateObject var viewModel = MyViewModel()
|
|
381
|
+
|
|
382
|
+
var body: some View {
|
|
383
|
+
// List does not update
|
|
384
|
+
List(viewModel.liveCollection.snapshots, id: \.commentId) { item in
|
|
385
|
+
Text("Comment: \(item.commentId)")
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**✅ Success: View where list updates successfully**
|
|
392
|
+
```swift
|
|
393
|
+
struct MyView2: View {
|
|
394
|
+
@StateObject var viewModel = MyViewModel()
|
|
395
|
+
|
|
396
|
+
var body: some View {
|
|
397
|
+
// List updates
|
|
398
|
+
CommentListView(commentCollection: viewModel.liveCollection)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
struct CommentListView: View {
|
|
403
|
+
@ObservedObject var commentCollection: AmityCollection<AmityComment>
|
|
404
|
+
|
|
405
|
+
var body: some View {
|
|
406
|
+
List(commentCollection.snapshots, id: \.commentId) { item in
|
|
407
|
+
Text("Comment: \(item.commentId)")
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
To solve this issue we recommend to create a specific view which observes AmityCollection & AmityObject as @ObservedObject as shown in code example.
|
|
414
|
+
|
|
415
|
+
Since the properties of AmityCollection & AmityObject are marked with @Published annotation, the publishing of changes occurs in the property's willSet block, meaning that any subscribers will receive an update before the property is changed. This behaviour can easily lead to unexpected bugs.
|
|
416
|
+
|
|
417
|
+
```swift
|
|
418
|
+
var cancellable: AnyCancellable?
|
|
419
|
+
func publishedPropertiesBugs() {
|
|
420
|
+
var liveObject: AmityObject<AmityComment>!
|
|
421
|
+
|
|
422
|
+
cancellable = liveObject.$snapshot.sink(receiveValue: { newComment in
|
|
423
|
+
|
|
424
|
+
var snapshot: AmityComment?
|
|
425
|
+
|
|
426
|
+
// Incorrect:
|
|
427
|
+
snapshot = liveObject.snapshot
|
|
428
|
+
|
|
429
|
+
// Correct:
|
|
430
|
+
snapshot = newComment
|
|
431
|
+
})
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## Best Practices
|
|
436
|
+
|
|
437
|
+
**Proper Retention**: Always retain `AmityNotificationToken` in appropriate scope to maintain observations. Store tokens as instance variables to prevent deallocation.
|
|
438
|
+
|
|
439
|
+
**Choose Appropriate Method**: Use `observe` for ongoing updates throughout the application lifecycle, and `observeOnce` for single-time data retrieval.
|
|
440
|
+
|
|
441
|
+
**Check Freshness**: Always verify `dataStatus` to determine if data is fresh or local before processing, especially when you need the most recent data.
|
|
442
|
+
|
|
443
|
+
**Dedicated Views**: Create dedicated views that observe live objects as `@ObservedObject` to avoid nested ObservableObject issues in SwiftUI.
|
|
444
|
+
|
|
445
|
+
**Parameter Usage**: When using Combine framework, always use parameter values in sink closures rather than accessing properties directly to avoid timing issues.
|
|
446
|
+
|
|
447
|
+
## Related Resources
|
|
448
|
+
|
|
449
|
+
Learn more about SwiftUI and ObservableObject
|
|
450
|
+
Explore Apple's reactive programming framework
|
|
451
|
+
|
|
452
|
+
---
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Live Objects & Collections
|
|
2
|
+
|
|
3
|
+
> Learn about observable data holders that provide real-time updates from local cache and server events
|
|
4
|
+
|
|
5
|
+
Live Objects and Live Collections are observable data holders. The observer gets updated when there is a change of data in the local cache.
|
|
6
|
+
|
|
7
|
+
There are two main sources that can make changes to the object and collection:
|
|
8
|
+
|
|
9
|
+
1. **When data get updated by an action from the current device**
|
|
10
|
+
2. **Real-time events of the subscribed data from the server**
|
|
11
|
+
|
|
12
|
+
Refer to the platform-specific subpages in this section for an in-depth explanation of how your SDK handles live objects and live collections.
|
|
13
|
+
|
|
14
|
+
By subscribing to a specific topic in the Real-time event system, users can ensure that they are receiving the most up-to-date information and notifications related to the observed object across devices.
|
|
15
|
+
|
|
16
|
+
## Data Sources
|
|
17
|
+
|
|
18
|
+
Updates from user interactions within your current device
|
|
19
|
+
Live changes from subscribed objects on the server
|
|
20
|
+
|
|
21
|
+
## Live Object
|
|
22
|
+
|
|
23
|
+
Live Object allows users to observe the states of a single object within the app, such as a post, message, or channel. With Live Object, users can automatically receive notifications of any data updates related to the observed object, whether from the network or locally.
|
|
24
|
+
|
|
25
|
+
### Key Features
|
|
26
|
+
|
|
27
|
+
Live Object API is designed around the observer design pattern, allowing users to subscribe to an object, receive notifications of any data updates, and unsubscribe when necessary.
|
|
28
|
+
|
|
29
|
+
Live Object also provides additional states, such as `isLoading` and `error`, which can help understand the status of the observed object and any potential issues that may arise.
|
|
30
|
+
|
|
31
|
+
These updates are delivered via Snapshot, a captured state that is frozen and cannot be changed. Users can own and access these snapshots to stay up-to-date with the latest data within the app.
|
|
32
|
+
|
|
33
|
+
It's worth noting that while data updates may occur internally within the SDK, these updates will not affect previous snapshots. The only way for users to see the latest data is by getting new snapshots delivered via Live Object updates.
|
|
34
|
+
|
|
35
|
+
### Use Cases
|
|
36
|
+
|
|
37
|
+
Live Object is ideal for monitoring individual entities such as:
|
|
38
|
+
|
|
39
|
+
- **Posts**: Track likes, comments, and content updates
|
|
40
|
+
- **Messages**: Monitor delivery status, read receipts, and reactions
|
|
41
|
+
- **Channels**: Observe member count changes and channel metadata
|
|
42
|
+
- **User Profiles**: Watch status updates, avatar changes, and online presence
|
|
43
|
+
|
|
44
|
+
### Live Object Architecture
|
|
45
|
+
|
|
46
|
+
```mermaid
|
|
47
|
+
graph TD
|
|
48
|
+
A[Your App UI] --> B[Live Object Observer]
|
|
49
|
+
B --> C[Snapshot Data]
|
|
50
|
+
C --> D[Local Cache]
|
|
51
|
+
C --> E[Real-time Events]
|
|
52
|
+
C --> F[Server Updates]
|
|
53
|
+
|
|
54
|
+
D --> G[User Actions]
|
|
55
|
+
E --> H[Other Users]
|
|
56
|
+
F --> I[Background Sync]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Live Collection
|
|
60
|
+
|
|
61
|
+
Live Collection allows users to observe changes to an entire collection of data within their app in real-time. Live Collection works similarly to Live Object, with the main difference being that Live Collection notifies changes related to the entire collection rather than just a single object.
|
|
62
|
+
|
|
63
|
+
### How It Works
|
|
64
|
+
|
|
65
|
+
To use Live Collection, users can observe changes in a specific collection within the SDK. The collection handler then receives a snapshot of changes since the last result, which includes three possible events:
|
|
66
|
+
|
|
67
|
+
The indices of the objects that were deleted
|
|
68
|
+
The indices of the objects that were inserted
|
|
69
|
+
The indices of the objects that were modified
|
|
70
|
+
|
|
71
|
+
### Change Detection
|
|
72
|
+
|
|
73
|
+
Live Collections provide granular change notifications instead of replacing entire datasets:
|
|
74
|
+
|
|
75
|
+
| Change Type | Description | Benefit |
|
|
76
|
+
|-------------|-------------|---------|
|
|
77
|
+
| **Insertions** | New items added to collection | Efficient UI updates for new content |
|
|
78
|
+
| **Deletions** | Items removed from collection | Smooth removal animations |
|
|
79
|
+
| **Modifications** | Existing items updated | Targeted updates without full refresh |
|
|
80
|
+
|
|
81
|
+
### Use Cases
|
|
82
|
+
|
|
83
|
+
By subscribing to changes on a specific collection using Live Collection, users can stay up-to-date with the latest changes and updates to important collections within the app. This can be particularly helpful for:
|
|
84
|
+
|
|
85
|
+
- **Managing large amounts of data**: Efficiently handle extensive datasets without performance impact
|
|
86
|
+
- **Monitoring frequently updated collections**: Track changes to dynamic content like messages or posts
|
|
87
|
+
- **Real-time social feeds**: Display new posts, comments, and reactions as they happen
|
|
88
|
+
- **Chat message lists**: Show new messages instantly with typing indicators
|
|
89
|
+
- **Member lists**: Track community or channel membership changes
|
|
90
|
+
|
|
91
|
+
### Live Collection Architecture
|
|
92
|
+
|
|
93
|
+
```mermaid
|
|
94
|
+
graph TD
|
|
95
|
+
A[Collection UI] --> B[Live Collection Observer]
|
|
96
|
+
B --> C[Change Snapshot]
|
|
97
|
+
C --> D[Insertions]
|
|
98
|
+
C --> E[Deletions]
|
|
99
|
+
C --> F[Modifications]
|
|
100
|
+
|
|
101
|
+
G[Data Sources] --> B
|
|
102
|
+
H[Paginated API] --> G
|
|
103
|
+
I[Real-time Events] --> G
|
|
104
|
+
J[Local Cache] --> G
|
|
105
|
+
K[User Actions] --> G
|
|
106
|
+
```
|
|
107
|
+
\
|
|
108
|
+
## Best Practices
|
|
109
|
+
|
|
110
|
+
**Proper Cleanup**: Always unsubscribe from observers to prevent memory leaks when components unmount or views disappear.
|
|
111
|
+
|
|
112
|
+
**Efficient Updates**: Use collection change indices for targeted UI updates instead of full re-renders. Only update the specific items that changed.
|
|
113
|
+
|
|
114
|
+
**Graceful Degradation**: Handle network errors and loading states appropriately using the built-in `isLoading` and `error` states.
|
|
115
|
+
|
|
116
|
+
**Topic Management**: Subscribe to relevant real-time topics for automatic updates. Ensure proper cleanup when no longer needed.
|
|
117
|
+
|
|
118
|
+
## Platform Implementation
|
|
119
|
+
|
|
120
|
+
Each platform has its own specific implementation patterns for Live Objects and Collections:
|
|
121
|
+
|
|
122
|
+
Learn iOS-specific patterns for observers and delegates
|
|
123
|
+
Discover Android LiveData and RxJava integration
|
|
124
|
+
Understand Dart streams and reactive widgets
|
|
125
|
+
|
|
126
|
+
## Related Concepts
|
|
127
|
+
|
|
128
|
+
Learn how Live Objects connect to real-time updates
|
|
129
|
+
Explore event channels powering live updates
|
|
130
|
+
|
|
131
|
+
**Development Tip**: Use platform-specific debugging tools to monitor subscription status and data flow in your Live Objects and Collections during development.
|
|
132
|
+
|
|
133
|
+
---
|