@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,236 @@
|
|
|
1
|
+
# Logging & Errors
|
|
2
|
+
|
|
3
|
+
> Monitor network activity and handle errors in the social.plus SDK using observeNetworkActivities().
|
|
4
|
+
|
|
5
|
+
# Logging
|
|
6
|
+
|
|
7
|
+
## Network Activity Monitoring
|
|
8
|
+
|
|
9
|
+
Use `observeNetworkActivities()` to inspect all HTTP requests and responses in your social.plus SDK integration during development.
|
|
10
|
+
|
|
11
|
+
**Best Practice**: Enable network logging during development to troubleshoot issues, but disable it in production to avoid performance overhead.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
The `observeNetworkActivities()` function provides real-time insights into all HTTP requests and responses within the SDK, enabling effective debugging and performance analysis.
|
|
15
|
+
|
|
16
|
+
### Key Features
|
|
17
|
+
|
|
18
|
+
- **Request/Response Logging**: Monitor all API calls with detailed information
|
|
19
|
+
- **Performance Metrics**: Track request timing and response sizes
|
|
20
|
+
- **Error Debugging**: Identify network-related issues quickly
|
|
21
|
+
- **Development Tool**: Essential for troubleshooting SDK integration
|
|
22
|
+
|
|
23
|
+
```swift iOS
|
|
24
|
+
/// Observe ongoing network activities in sdk
|
|
25
|
+
client.observeNetworkActivities { request, response, data in
|
|
26
|
+
|
|
27
|
+
// request: URLRequest // Request
|
|
28
|
+
// response: HTTPURLResponse // HTTP Protocol response
|
|
29
|
+
// data: Data // Payload returned from server
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```kotlin Android
|
|
35
|
+
/// Observe ongoing network activities in sdk
|
|
36
|
+
AmityCoreClient.observeNetworkActivities()
|
|
37
|
+
.doOnNext { activity ->
|
|
38
|
+
(activity as? AmityNetworkActivity.HTTP)?.run {
|
|
39
|
+
val response: Response = activity.response
|
|
40
|
+
val request = response.request
|
|
41
|
+
val processedText =
|
|
42
|
+
"Request: ${request.method} ${request.url}\n" +
|
|
43
|
+
"(${DateTime(response.sentRequestAtMillis)})\n" +
|
|
44
|
+
"Response: ${response.code} ${response.message}\n" +
|
|
45
|
+
"(${DateTime(response.receivedResponseAtMillis)})\n"
|
|
46
|
+
print(processedText)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
.doOnError {
|
|
50
|
+
// Exception
|
|
51
|
+
}
|
|
52
|
+
.subscribe()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```typescript TypeScript
|
|
56
|
+
import { Client } from '@amityco/ts-sdk';
|
|
57
|
+
|
|
58
|
+
/// Observe ongoing network activities in sdk
|
|
59
|
+
Client.onNetworkActivities((request, response) => {
|
|
60
|
+
console.log(request);
|
|
61
|
+
console.log(response);
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Troubleshooting
|
|
66
|
+
|
|
67
|
+
### Common Issues
|
|
68
|
+
|
|
69
|
+
Network logging stores request/response data in memory. Disable logging in production or implement log rotation for long-running applications.
|
|
70
|
+
|
|
71
|
+
Logging adds overhead to network operations. Use conditional compilation flags to disable in release builds.
|
|
72
|
+
|
|
73
|
+
social.plus apps can generate many API calls. Implement filtering to log only specific endpoints or error conditions.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Error Handling
|
|
77
|
+
|
|
78
|
+
Effective error handling is crucial for building reliable social applications. social.plus SDK provides structured error information to help you handle different scenarios gracefully.
|
|
79
|
+
|
|
80
|
+
## Error Types
|
|
81
|
+
|
|
82
|
+
social.plus errors are categorized into two main types:
|
|
83
|
+
|
|
84
|
+
Issues on the social.plus backend (400xxx, 500xxx codes)
|
|
85
|
+
|
|
86
|
+
Issues in the SDK or client environment (800xxx codes)
|
|
87
|
+
|
|
88
|
+
## Common Server Errors
|
|
89
|
+
|
|
90
|
+
| Code | Error | Description |
|
|
91
|
+
|------|-------|-------------|
|
|
92
|
+
| 400100 | `UnauthorizedError` | User not authenticated or invalid token |
|
|
93
|
+
| 400101 | `TokenExpired` | Authentication token has expired |
|
|
94
|
+
|
|
95
|
+
| Code | Error | Description |
|
|
96
|
+
|------|-------|-------------|
|
|
97
|
+
| 400301 | `PermissionDenied` | User lacks permission for the action |
|
|
98
|
+
| 400302 | `UserIsMuted` | Muted user attempting to send message |
|
|
99
|
+
| 400304 | `UserIsBanned` | Banned user accessing restricted content |
|
|
100
|
+
| 400312 | `GlobalBanError` | Globally banned user performing any action |
|
|
101
|
+
|
|
102
|
+
| Code | Error | Description |
|
|
103
|
+
|------|-------|-------------|
|
|
104
|
+
| 400307 | `MaxRepetitionExceed` | Too many messages with blocked content |
|
|
105
|
+
| 400308 | `BanWordFound` | Message contains blocked words |
|
|
106
|
+
| 400309 | `LinkNotAllowed` | Message contains non-whitelisted links |
|
|
107
|
+
| 400314 | `UnsafeContent` | Content flagged by AI moderation |
|
|
108
|
+
|
|
109
|
+
| Code | Error | Description |
|
|
110
|
+
|------|-------|-------------|
|
|
111
|
+
| 400000 | `BadRequestError` | Invalid request parameters |
|
|
112
|
+
| 400315 | `DuplicateEntryError` | Duplicate display name or identifier |
|
|
113
|
+
| 400400 | `ItemNotFound` | Requested resource not found |
|
|
114
|
+
| 400900 | `Conflict` | Conflicting data operation |
|
|
115
|
+
|
|
116
|
+
| Code | Error | Description |
|
|
117
|
+
|------|-------|-------------|
|
|
118
|
+
| 500000 | `BusinessError` | Internal system error |
|
|
119
|
+
|
|
120
|
+
## Client Errors
|
|
121
|
+
|
|
122
|
+
| Code | Error | Description |
|
|
123
|
+
|------|-------|-------------|
|
|
124
|
+
| 800000 | `Unknown` | Uncategorized client-side error |
|
|
125
|
+
| 800110 | `InvalidParameter` | Invalid parameter data type |
|
|
126
|
+
| 800210 | `ConnectionError` | Network connectivity issues |
|
|
127
|
+
|
|
128
|
+
## Error Handling Implementation
|
|
129
|
+
|
|
130
|
+
### Basic Error Parsing
|
|
131
|
+
```swift iOS
|
|
132
|
+
func parseError(_ error: Error) {
|
|
133
|
+
let sdkError = error as NSError
|
|
134
|
+
guard let amityError = AmityErrorCode(rawValue: sdkError.code) else {
|
|
135
|
+
// Unknown error occurred. Please report this error code to Amity
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ...
|
|
140
|
+
// Process AmityError here..
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```kotlin Android
|
|
145
|
+
fun parseAmityError(exception: Exception) {
|
|
146
|
+
if (AmityError.from(exception) == AmityError.CHANNEL_IS_MUTED) {
|
|
147
|
+
// Couldn't send a message to this channel because this channel is muted.
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
### Global Ban Handling
|
|
154
|
+
|
|
155
|
+
A global ban error means that the user is banned from the system resulting in the inability to have a connection with the system. If the user already has a session, the session will be revoked, and will be unable to create a new session.
|
|
156
|
+
|
|
157
|
+
```swift iOS
|
|
158
|
+
var client: AmityClient?
|
|
159
|
+
client.clientErrorDelegate = self // set yourself as the delegate
|
|
160
|
+
|
|
161
|
+
...
|
|
162
|
+
|
|
163
|
+
// Implement this delegate method which gets called when error occurs
|
|
164
|
+
func didReceiveAsyncError(_ error: Error) {
|
|
165
|
+
let error = error as NSError
|
|
166
|
+
guard let amityError = AmityErrorCode(rawValue: error.code) else {
|
|
167
|
+
assertionFailure("unknown error \(error.code), please report this code to Amity")
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if amityError == .globalBan {
|
|
172
|
+
// Handle global ban event here
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```kotlin Android
|
|
179
|
+
//error handling upon login
|
|
180
|
+
fun login() {
|
|
181
|
+
AmityCoreClient.login(userId = "userId")
|
|
182
|
+
.build()
|
|
183
|
+
.submit()
|
|
184
|
+
.doOnError {
|
|
185
|
+
if (AmityError.from(it) == AmityError.USER_IS_GLOBAL_BANNED) {
|
|
186
|
+
// handle the case the user is globally banned
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
.subscribe()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//error handling thru real-time events
|
|
193
|
+
fun subscribeGlobalBan() {
|
|
194
|
+
// Subscription can be done at Application lifecycle
|
|
195
|
+
// and lives through the remaining Application lifecycle
|
|
196
|
+
AmityCoreClient.getGlobalBanEvents()
|
|
197
|
+
.doOnNext {
|
|
198
|
+
// handle the case the user is globally banned
|
|
199
|
+
}
|
|
200
|
+
.subscribe()
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```typescript TypeScript
|
|
205
|
+
import { Client } from '@amityco/ts-sdk';
|
|
206
|
+
|
|
207
|
+
Client.onConnectionError(error => {
|
|
208
|
+
if (error.code === Amity.ServerError.GLOBAL_BAN) {
|
|
209
|
+
console.log('user has been globally banned!');
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
## Best Practices
|
|
216
|
+
|
|
217
|
+
### Do's ✅
|
|
218
|
+
|
|
219
|
+
- **Provide specific error messages** based on error codes
|
|
220
|
+
- **Log errors** for debugging and monitoring
|
|
221
|
+
- **Implement retry logic** for network errors
|
|
222
|
+
- **Handle global bans** with appropriate user communication
|
|
223
|
+
- **Use error boundaries** in React applications
|
|
224
|
+
- **Show loading states** during error recovery
|
|
225
|
+
|
|
226
|
+
### Don'ts ❌
|
|
227
|
+
|
|
228
|
+
- **Don't ignore errors** - always handle them appropriately
|
|
229
|
+
- **Don't show technical error codes** to end users
|
|
230
|
+
- **Don't retry indefinitely** - implement maximum retry limits
|
|
231
|
+
- **Don't expose sensitive information** in error messages
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
**Pro Tip**: Use network logging to identify performance bottlenecks, monitor error rates, and validate that your app is making expected API calls during development.
|
|
235
|
+
|
|
236
|
+
---
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# Android Live Objects/Collections
|
|
2
|
+
|
|
3
|
+
> Live Objects are supported in the Android SDK with RxJava Data Streaming
|
|
4
|
+
|
|
5
|
+
The RxJava3 library is being used in Android development to achieve Live Object and Live Collection behavior.
|
|
6
|
+
|
|
7
|
+
It is a Java VM implementation of ReactiveX, a library for composing asynchronous and event-based programs by using observable sequences. The building blocks of RxJava are Observables and Subscribers. Observable is used for emitting items and Subscriber is used for consuming those items.
|
|
8
|
+
|
|
9
|
+
You can visit [ReactiveX](https://reactivex.io/) for more information.
|
|
10
|
+
|
|
11
|
+
## How it Works
|
|
12
|
+
|
|
13
|
+
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.
|
|
14
|
+
|
|
15
|
+
Rx3 Data Stream helps in syncing the data so you will always get the most recent one. Whenever the data updates, you will be notified through **Flowable Objects** and **Flowable Collection**.
|
|
16
|
+
|
|
17
|
+
New data gets automatically collected everytime when there is an updation and user need not refresh to get the recent data.
|
|
18
|
+
|
|
19
|
+
### Data Sources
|
|
20
|
+
|
|
21
|
+
Data present in local storage
|
|
22
|
+
Data queried from the server
|
|
23
|
+
Data received from real-time events
|
|
24
|
+
|
|
25
|
+
## How to Retrieve Data from Rx3 Data Stream
|
|
26
|
+
|
|
27
|
+
To retrieve data from the RxStream, we need to subscribe to the Stream(Flowable/Single/Completable) by defining subscribing and observing threads.
|
|
28
|
+
|
|
29
|
+
```kotlin
|
|
30
|
+
fun subscribeToFlowable() {
|
|
31
|
+
//Flowable
|
|
32
|
+
val flowableStream: Flowable<Any> =
|
|
33
|
+
Flowable.just("one", "two", "three") // Flowable initialization
|
|
34
|
+
flowableStream
|
|
35
|
+
.subscribeOn(Schedulers.io()) // subscribing an operation on io thread (Background thread)
|
|
36
|
+
.observeOn(AndroidSchedulers.mainThread()) // observing results on main thread (UI thread)
|
|
37
|
+
.doOnNext {
|
|
38
|
+
// data is available here
|
|
39
|
+
}.doOnError {
|
|
40
|
+
// handle error here
|
|
41
|
+
}.subscribe()
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Events a Data Stream can Emit
|
|
46
|
+
|
|
47
|
+
In the RxJava3 framework we have these different types of objects that can be observed:
|
|
48
|
+
|
|
49
|
+
### Flowable
|
|
50
|
+
Emits a stream of elements
|
|
51
|
+
|
|
52
|
+
- **doOnNext**
|
|
53
|
+
- **doOnError**
|
|
54
|
+
|
|
55
|
+
### Single
|
|
56
|
+
Emits exactly one element
|
|
57
|
+
|
|
58
|
+
- **doOnSuccess**
|
|
59
|
+
- **doOnError**
|
|
60
|
+
|
|
61
|
+
### Completable
|
|
62
|
+
Emits a "complete" event, without emitting any data type, just a success/failure
|
|
63
|
+
|
|
64
|
+
- **doOnComplete**
|
|
65
|
+
- **doOnError**
|
|
66
|
+
|
|
67
|
+
```kotlin
|
|
68
|
+
val flowableStream: Flowable<List<AmityPost>> = postRepository.getPosts()
|
|
69
|
+
|
|
70
|
+
flowableStream
|
|
71
|
+
.subscribeOn(Schedulers.io())
|
|
72
|
+
.observeOn(AndroidSchedulers.mainThread())
|
|
73
|
+
.doOnNext { posts ->
|
|
74
|
+
// Handle stream of posts
|
|
75
|
+
updateUI(posts)
|
|
76
|
+
}
|
|
77
|
+
.doOnError { error ->
|
|
78
|
+
// Handle error
|
|
79
|
+
showErrorMessage(error.message)
|
|
80
|
+
}
|
|
81
|
+
.subscribe()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```kotlin
|
|
85
|
+
val singlePost: Single<AmityPost> = postRepository.getPost(postId)
|
|
86
|
+
|
|
87
|
+
singlePost
|
|
88
|
+
.subscribeOn(Schedulers.io())
|
|
89
|
+
.observeOn(AndroidSchedulers.mainThread())
|
|
90
|
+
.doOnSuccess { post ->
|
|
91
|
+
// Handle single post
|
|
92
|
+
displayPost(post)
|
|
93
|
+
}
|
|
94
|
+
.doOnError { error ->
|
|
95
|
+
// Handle error
|
|
96
|
+
showErrorMessage(error.message)
|
|
97
|
+
}
|
|
98
|
+
.subscribe()
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```kotlin
|
|
102
|
+
val deleteOperation: Completable = postRepository.deletePost(postId)
|
|
103
|
+
|
|
104
|
+
deleteOperation
|
|
105
|
+
.subscribeOn(Schedulers.io())
|
|
106
|
+
.observeOn(AndroidSchedulers.mainThread())
|
|
107
|
+
.doOnComplete {
|
|
108
|
+
// Handle successful completion
|
|
109
|
+
showSuccessMessage("Operation completed")
|
|
110
|
+
}
|
|
111
|
+
.doOnError { error ->
|
|
112
|
+
// Handle error
|
|
113
|
+
showErrorMessage(error.message)
|
|
114
|
+
}
|
|
115
|
+
.subscribe()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Flow functions
|
|
119
|
+
|
|
120
|
+
By using the `.asFlow()` method, it enables the conversion of Flowable<T> functions of the Amity Android SDK into Flow functions.
|
|
121
|
+
|
|
122
|
+
```kotlin
|
|
123
|
+
fun getAllUsers(): Flow<PagingData<AmityUser>> {
|
|
124
|
+
return AmityCoreClient.newUserRepository()
|
|
125
|
+
.getUsers()
|
|
126
|
+
.build()
|
|
127
|
+
.query()
|
|
128
|
+
.asFlow()
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Jetpack Compose compatibility
|
|
133
|
+
|
|
134
|
+
Amity Android SDK seamlessly integrates with Jetpack Compose UI, allowing you to take full advantage of the modern UI toolkit provided by Jetpack Compose. You can effortlessly incorporate our SDK into your Jetpack Compose-based projects to enhance your app's social experience. This compatibility ensures that you can leverage the power of Jetpack Compose while benefiting from the features and capabilities our SDK provides.
|
|
135
|
+
|
|
136
|
+
### Flow of PagingData in Compose
|
|
137
|
+
|
|
138
|
+
In Jetpack Compose, integrating data from a `Flow<PagingData<T>>` source into your UI is made easy through the `collectAsLazyPagingItems()` function. This function allows you to seamlessly paginate and display items within your Composable functions.
|
|
139
|
+
|
|
140
|
+
To start using it, add compose paging dependency in your project app level build.gradle file.
|
|
141
|
+
|
|
142
|
+
```gradle
|
|
143
|
+
implementation "androidx.paging:paging-compose:x.y.z"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Then in your Composable functions, you can collect from flow and display data, and also can observe the load state.
|
|
147
|
+
|
|
148
|
+
```kotlin
|
|
149
|
+
@Composable
|
|
150
|
+
fun UserList(
|
|
151
|
+
modifier: Modifier = Modifier
|
|
152
|
+
) {
|
|
153
|
+
// collect data from flow using collectAsLazyPagingItems
|
|
154
|
+
val users = getAllUsers().collectAsLazyPagingItems()
|
|
155
|
+
|
|
156
|
+
// display data in LazyColumn or LazyRow
|
|
157
|
+
LazyColumn(
|
|
158
|
+
modifier = modifier.fillMaxSize(),
|
|
159
|
+
horizontalAlignment = Alignment.CenterHorizontally,
|
|
160
|
+
) {
|
|
161
|
+
items(
|
|
162
|
+
count = users.itemCount,
|
|
163
|
+
key = users.itemKey { it.getUserId() }
|
|
164
|
+
) { index ->
|
|
165
|
+
// render each item here
|
|
166
|
+
val user = users[index]
|
|
167
|
+
Text(text = "UserId: ${user?.getUserId()}")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// handle load state on loading first page
|
|
171
|
+
when (val state = users.loadState.refresh) {
|
|
172
|
+
is LoadState.Error -> {
|
|
173
|
+
// handle error
|
|
174
|
+
item {
|
|
175
|
+
Text(text = "Error: ${state.error.message}")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
is LoadState.Loading -> {
|
|
180
|
+
// loading first page
|
|
181
|
+
item {
|
|
182
|
+
Column(
|
|
183
|
+
modifier = modifier.fillParentMaxSize(),
|
|
184
|
+
horizontalAlignment = Alignment.CenterHorizontally,
|
|
185
|
+
verticalArrangement = Arrangement.Center,
|
|
186
|
+
) {
|
|
187
|
+
Text(text = "Refresh/First Loading")
|
|
188
|
+
CircularProgressIndicator(color = Color.Black)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
else -> {}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// handle load state on loading next page
|
|
197
|
+
when (val state = users.loadState.append) {
|
|
198
|
+
is LoadState.Error -> {
|
|
199
|
+
// handle error
|
|
200
|
+
item {
|
|
201
|
+
Text(text = "Error: ${state.error.message}")
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
is LoadState.Loading -> {
|
|
206
|
+
// loading next page
|
|
207
|
+
item {
|
|
208
|
+
Column(
|
|
209
|
+
modifier = modifier.fillMaxWidth(),
|
|
210
|
+
horizontalAlignment = Alignment.CenterHorizontally,
|
|
211
|
+
verticalArrangement = Arrangement.Center,
|
|
212
|
+
) {
|
|
213
|
+
Text(text = "Pagination Loading")
|
|
214
|
+
CircularProgressIndicator(color = Color.Black)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
else -> {}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Flow in Compose
|
|
226
|
+
|
|
227
|
+
By using `collectAsState()` method, it can deliver asynchronous data updates to your Compose UI components.
|
|
228
|
+
|
|
229
|
+
```kotlin
|
|
230
|
+
fun getPostAsFlow(): Flow<AmityPost> {
|
|
231
|
+
return AmitySocialClient.newPostRepository()
|
|
232
|
+
.getPost(postId = "postId")
|
|
233
|
+
.asFlow()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@Composable
|
|
237
|
+
fun SinglePostItem() {
|
|
238
|
+
val post by getPostAsFlow().collectAsState(initial = null)
|
|
239
|
+
|
|
240
|
+
Text(text = "Post ID: ${post?.getPostId()}")
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
## Best Practices
|
|
246
|
+
|
|
247
|
+
**Proper Disposal**: Always dispose of RxJava subscriptions to prevent memory leaks when Activities/Fragments are destroyed.
|
|
248
|
+
|
|
249
|
+
**Proper Scheduling**: Use `subscribeOn(Schedulers.io())` for background operations and `observeOn(AndroidSchedulers.mainThread())` for UI updates.
|
|
250
|
+
|
|
251
|
+
**Graceful Recovery**: Implement robust error handling with `doOnError` for all RxJava streams to prevent crashes.
|
|
252
|
+
|
|
253
|
+
**State Management**: Use `collectAsState()` for single objects and `collectAsLazyPagingItems()` for paginated collections in Compose.
|
|
254
|
+
|
|
255
|
+
## Related Resources
|
|
256
|
+
|
|
257
|
+
Learn more about reactive programming concepts
|
|
258
|
+
Explore Android's official paging library
|
|
259
|
+
Build modern Android UI with declarative programming
|
|
260
|
+
Access RxJava3 source code and documentation
|
|
261
|
+
|
|
262
|
+
---
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Flutter Live Objects/Collections
|
|
2
|
+
|
|
3
|
+
> Live Objects are supported in the Flutter SDK with Streams and LiveCollection for real-time data synchronization
|
|
4
|
+
|
|
5
|
+
In Flutter SDK, we have a concept of **Live Object** and **Live Collection**.
|
|
6
|
+
|
|
7
|
+
Live Object is represented by `StreamSubscription<Object>` instance and Live Collection is represented by an instance of `LiveCollection`. LiveCollection is a generic class that encapsulates any other object and notifies the observer whenever any property of the encapsulated object changes.
|
|
8
|
+
|
|
9
|
+
Live Object helps to observe changes in a single object whereas Live Collection helps to observe changes in a list of objects.
|
|
10
|
+
|
|
11
|
+
Examples: `StreamSubscription<AmityPost>` for single objects or `LiveCollection<AmityMessage>` for collections.
|
|
12
|
+
|
|
13
|
+
## How it Works
|
|
14
|
+
|
|
15
|
+
SDK handles lots of data received from various sources. Data can be present in the local cache. It might also be queried from the server or received from some real-time events. What this means is that the same data is constantly updating. The data that you are accessing at the moment can get updated by other sources and become out of sync.
|
|
16
|
+
|
|
17
|
+
Live Object and Live Collection help in syncing the data so you will always get the most recent one. Whenever the data updates, you will be notified through helper methods in the Live Object and Live Collection classes.
|
|
18
|
+
|
|
19
|
+
New data gets automatically collected every time when there is an update 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
|
+
## LiveObject
|
|
28
|
+
|
|
29
|
+
`StreamSubscription<Object>` is a native flutter class that keeps track of a single object. It is a live object. In Flutter AmitySDK, any object which provides Stream is a Live Object.
|
|
30
|
+
|
|
31
|
+
This function helps listen to Live Object. Whenever any property for the observed object changes, the listen callback will be triggered.
|
|
32
|
+
|
|
33
|
+
### Example Usage
|
|
34
|
+
|
|
35
|
+
```dart
|
|
36
|
+
void listenPost(String postId) {
|
|
37
|
+
AmitySocialClient.newPostRepository()
|
|
38
|
+
.getPostStream(postId)
|
|
39
|
+
.stream
|
|
40
|
+
.listen((AmityPost post) {
|
|
41
|
+
//handle results
|
|
42
|
+
}).onError((error, stackTrace) {
|
|
43
|
+
//handle error
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Live Collection
|
|
49
|
+
|
|
50
|
+
`LiveCollection` is a generic class that keeps track of a collection of objects. It is a Live Collection. In Flutter SDK, any object that is encapsulated by LiveCollection class is a live collection.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Stream Observer
|
|
54
|
+
|
|
55
|
+
`asStream` method can get triggered multiple times throughout the lifetime of the application as long as its associated `Stream<List<Object>>` is retained in memory.
|
|
56
|
+
|
|
57
|
+
`asStream` method will be called from the main thread so you can perform any UI update-related task within the listen block itself.
|
|
58
|
+
|
|
59
|
+
- If the requested data collection is stored locally on the device, the block will be called immediately with the local version of the data.
|
|
60
|
+
- In parallel, a request is made to the server to fetch the latest version of the data. Once the data is returned, the listen block will be triggered again.
|
|
61
|
+
- Any future changes to the data from any sources can trigger the listen block.
|
|
62
|
+
|
|
63
|
+
### Pagination
|
|
64
|
+
|
|
65
|
+
AmityCollection in SDK returns a maximum of pageSize items per page. It has `loadNext()` method to fetch more data. It also exposes `hasNext` property to check if the next page or previous page is present.
|
|
66
|
+
|
|
67
|
+
```dart
|
|
68
|
+
void loadNextPage() async {
|
|
69
|
+
if (liveCollection.hasNextPage()) {
|
|
70
|
+
liveCollection.loadNext();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Once the next page is available, the same listen block gets triggered and you can access the collection as shown above. If you want to shrink the collection to the original first page, you can do so by calling `reset()` method on the same collection.
|
|
76
|
+
|
|
77
|
+
### ListView Integration
|
|
78
|
+
|
|
79
|
+
One typical usage of LiveCollection is in ListView. Below is an example of fetching a collection and updating its state in a Widget.
|
|
80
|
+
|
|
81
|
+
```dart
|
|
82
|
+
void initState(String channelId) {
|
|
83
|
+
messageLiveCollection = AmityChatClient.newMessageRepository()
|
|
84
|
+
.getMessages(channelId)
|
|
85
|
+
.stackFromEnd(true)
|
|
86
|
+
.getLiveCollection(pageSize: 20);
|
|
87
|
+
|
|
88
|
+
messageLiveCollection.getStreamController().stream.listen((event) {
|
|
89
|
+
// update latest results here
|
|
90
|
+
// setState(() {
|
|
91
|
+
// amityMessages = event;
|
|
92
|
+
// });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
//load first page when initiating widget
|
|
96
|
+
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
|
97
|
+
messageLiveCollection.loadNext();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
scrollcontroller.addListener(paginationListener);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
void paginationListener() {
|
|
104
|
+
if ((scrollcontroller.position.pixels >=
|
|
105
|
+
(scrollcontroller.position.maxScrollExtent - 100)) &&
|
|
106
|
+
messageLiveCollection.hasNextPage()) {
|
|
107
|
+
messageLiveCollection.loadNext();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Best Practices
|
|
113
|
+
|
|
114
|
+
**Always Cancel Subscriptions**: Prevent memory leaks by properly canceling StreamSubscriptions in the dispose method.
|
|
115
|
+
|
|
116
|
+
```dart
|
|
117
|
+
class MyWidget extends StatefulWidget {
|
|
118
|
+
@override
|
|
119
|
+
_MyWidgetState createState() => _MyWidgetState();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class _MyWidgetState extends State<MyWidget> {
|
|
123
|
+
late StreamSubscription<AmityPost> _postSubscription;
|
|
124
|
+
late LiveCollection<AmityMessage> _messageLiveCollection;
|
|
125
|
+
|
|
126
|
+
@override
|
|
127
|
+
void dispose() {
|
|
128
|
+
// Cancel stream subscriptions
|
|
129
|
+
_postSubscription.cancel();
|
|
130
|
+
|
|
131
|
+
// Reset live collections
|
|
132
|
+
_messageLiveCollection.reset();
|
|
133
|
+
|
|
134
|
+
super.dispose();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Handle Stream Errors**: Always provide error callbacks for streams to prevent crashes.
|
|
140
|
+
|
|
141
|
+
```dart
|
|
142
|
+
_postSubscription = repository.getPost(postId).listen(
|
|
143
|
+
(post) {
|
|
144
|
+
// Handle success
|
|
145
|
+
setState(() {
|
|
146
|
+
_post = post;
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
onError: (error) {
|
|
150
|
+
// Handle error gracefully
|
|
151
|
+
setState(() {
|
|
152
|
+
_error = error.toString();
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Check hasNextPage**: Always verify if more pages are available before loading to avoid unnecessary API calls.
|
|
159
|
+
|
|
160
|
+
```dart
|
|
161
|
+
void loadMoreData() {
|
|
162
|
+
if (liveCollection.hasNextPage() && !_isLoading) {
|
|
163
|
+
setState(() {
|
|
164
|
+
_isLoading = true;
|
|
165
|
+
});
|
|
166
|
+
liveCollection.loadNext();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Use StreamBuilder**: Leverage Flutter's StreamBuilder for efficient UI updates without manual setState calls.
|
|
172
|
+
|
|
173
|
+
```dart
|
|
174
|
+
StreamBuilder<AmityPost>(
|
|
175
|
+
stream: repository.getPost(postId),
|
|
176
|
+
builder: (context, snapshot) {
|
|
177
|
+
if (snapshot.hasError) {
|
|
178
|
+
return ErrorWidget(snapshot.error!);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!snapshot.hasData) {
|
|
182
|
+
return CircularProgressIndicator();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return PostWidget(post: snapshot.data!);
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Related Resources
|
|
191
|
+
|
|
192
|
+
Understanding Dart Streams and StreamSubscriptions
|
|
193
|
+
Flutter's StreamBuilder for reactive UI updates
|
|
194
|
+
|
|
195
|
+
---
|