Skip to content

Commit 17191c9

Browse files
Merge pull request #125 from sendbird/v4.2.25
Add 4.2.25.
2 parents 1727cfb + a0cfc84 commit 17191c9

12 files changed

+127
-36
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## v4.2.25 (Oct 21, 2024)
2+
3+
### Improvements
4+
- Fixed the reconnection bug while entering foreground
5+
- Modified the endpoint of sbm metric
6+
17
## v4.2.24 (Oct 8, 2024)
28

39
### Improvements

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Before installing Sendbird Chat SDK, you need to create a Sendbird application o
5050

5151
```yaml
5252
dependencies:
53-
sendbird_chat_sdk: ^4.2.24
53+
sendbird_chat_sdk: ^4.2.25
5454
```
5555
5656
- Run `flutter pub get` command in your project directory.

lib/src/internal/main/chat/chat.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ part 'chat_notifications.dart';
6262
part 'chat_push.dart';
6363
part 'chat_user.dart';
6464

65-
const sdkVersion = '4.2.24';
65+
const sdkVersion = '4.2.25';
6666

6767
// Internal implementation for main class. Do not directly access this class.
6868
class Chat with WidgetsBindingObserver {
@@ -91,6 +91,7 @@ class Chat with WidgetsBindingObserver {
9191

9292
bool? _isObserverRegistered;
9393
int lastMarkAsReadTimestamp;
94+
bool isBackground = false;
9495

9596
// This allows a value of type T or T? to be treated as a value of type T?.
9697
// We use this so that APIs that have become non-nullable can still be used
@@ -176,8 +177,13 @@ class Chat with WidgetsBindingObserver {
176177
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
177178
sbLog.i(StackTrace.current, 'state: $state');
178179

179-
if (state == AppLifecycleState.paused) await _handleEnterBackground();
180-
if (state == AppLifecycleState.resumed) await _handleEnterForeground();
180+
if (state == AppLifecycleState.paused) {
181+
isBackground = true;
182+
await _handleEnterBackground();
183+
} else if (state == AppLifecycleState.resumed) {
184+
isBackground = false;
185+
await _handleEnterForeground();
186+
}
181187
}
182188

183189
Future<void> _handleEnterBackground() async {

lib/src/internal/main/chat_manager/connection_manager.dart

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,11 @@ class ConnectionManager {
8383
);
8484
}
8585

86-
Future<void> disconnect({required logout}) async {
86+
Future<void> disconnect({required bool logout}) async {
8787
await _currentState.disconnect(logout: logout);
8888
}
8989

90-
Future<bool> reconnect({bool reset = false}) async {
90+
Future<bool> reconnect({required bool reset}) async {
9191
return await _currentState.reconnect(reset: reset);
9292
}
9393

@@ -242,6 +242,7 @@ class ConnectionManager {
242242
Future<void> doDisconnect({
243243
required bool clear,
244244
bool logout = false,
245+
bool fromEnterBackground = false,
245246
}) async {
246247
sbLog.i(
247248
StackTrace.current,
@@ -262,7 +263,7 @@ class ConnectionManager {
262263
}
263264
}
264265

265-
await webSocketClient.close();
266+
final isClosedSuccessfully = await webSocketClient.close();
266267

267268
final disconnectedUserId = chat.chatContext.currentUserId ?? '';
268269

@@ -294,10 +295,14 @@ class ConnectionManager {
294295
await chat.eventDispatcher.onDisconnected();
295296
}
296297

297-
changeState(DisconnectedState(chat: chat));
298+
if (fromEnterBackground && !chat.isBackground && !isClosedSuccessfully) {
299+
chat.connectionManager.reconnect(reset: true); // Check
300+
} else {
301+
changeState(DisconnectedState(chat: chat));
298302

299-
if (clear && disconnectedUserId.isNotEmpty) {
300-
chat.eventManager.notifyDisconnected(disconnectedUserId);
303+
if (clear && disconnectedUserId.isNotEmpty) {
304+
chat.eventManager.notifyDisconnected(disconnectedUserId);
305+
}
301306
}
302307
}
303308

lib/src/internal/main/connection_state/base_connection_state.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ abstract class BaseConnectionState {
1515
String? apiHost,
1616
String? wsHost,
1717
});
18-
Future<void> disconnect({required logout});
19-
Future<bool> reconnect({bool reset = false});
18+
Future<void> disconnect({required bool logout});
19+
Future<bool> reconnect({required bool reset});
2020

2121
Future<void> enterBackground();
2222
Future<void> enterForeground();

lib/src/internal/main/connection_state/connected_state.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class ConnectedState extends BaseConnectionState {
3737
}
3838

3939
@override
40-
Future<void> disconnect({required logout}) async {
40+
Future<void> disconnect({required bool logout}) async {
4141
sbLog.i(StackTrace.current);
4242
await chat.connectionManager.doDisconnect(clear: logout, logout: logout);
4343
}
@@ -51,7 +51,8 @@ class ConnectedState extends BaseConnectionState {
5151
@override
5252
Future<void> enterBackground() async {
5353
sbLog.i(StackTrace.current);
54-
await chat.connectionManager.doDisconnect(clear: false);
54+
await chat.connectionManager
55+
.doDisconnect(clear: false, fromEnterBackground: true);
5556
}
5657

5758
@override

lib/src/internal/main/connection_state/connecting_state.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class ConnectingState extends BaseConnectionState {
3838
}
3939

4040
@override
41-
Future<void> disconnect({required logout}) async {
41+
Future<void> disconnect({required bool logout}) async {
4242
sbLog.i(StackTrace.current);
4343
await chat.connectionManager.doDisconnect(clear: logout, logout: logout);
4444
}
@@ -52,7 +52,8 @@ class ConnectingState extends BaseConnectionState {
5252
@override
5353
Future<void> enterBackground() async {
5454
sbLog.i(StackTrace.current);
55-
await chat.connectionManager.doDisconnect(clear: false);
55+
await chat.connectionManager
56+
.doDisconnect(clear: false, fromEnterBackground: true);
5657
}
5758

5859
@override

lib/src/internal/main/connection_state/disconnected_state.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class DisconnectedState extends BaseConnectionState {
3737
}
3838

3939
@override
40-
Future<void> disconnect({required logout}) async {
40+
Future<void> disconnect({required bool logout}) async {
4141
sbLog.i(StackTrace.current);
4242
await chat.connectionManager.doDisconnect(clear: logout, logout: logout);
4343
}
@@ -51,7 +51,8 @@ class DisconnectedState extends BaseConnectionState {
5151
@override
5252
Future<void> enterBackground() async {
5353
sbLog.i(StackTrace.current);
54-
await chat.connectionManager.doDisconnect(clear: false);
54+
await chat.connectionManager
55+
.doDisconnect(clear: false, fromEnterBackground: true);
5556
}
5657

5758
@override

lib/src/internal/main/connection_state/reconnecting_state.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class ReconnectingState extends BaseConnectionState {
3636
}
3737

3838
@override
39-
Future<void> disconnect({required logout}) async {
39+
Future<void> disconnect({required bool logout}) async {
4040
sbLog.i(StackTrace.current);
4141
await chat.connectionManager.doDisconnect(clear: logout, logout: logout);
4242
}
@@ -50,7 +50,8 @@ class ReconnectingState extends BaseConnectionState {
5050
@override
5151
Future<void> enterBackground() async {
5252
sbLog.i(StackTrace.current);
53-
await chat.connectionManager.doDisconnect(clear: false);
53+
await chat.connectionManager
54+
.doDisconnect(clear: false, fromEnterBackground: true);
5455
}
5556

5657
@override

lib/src/internal/main/stats/stat_manager.dart

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/stats/stat_state.dart';
1717
import 'package:sendbird_chat_sdk/src/internal/main/stats/stat_type.dart';
1818
import 'package:sendbird_chat_sdk/src/internal/main/stats/stat_utils.dart';
1919
import 'package:sendbird_chat_sdk/src/internal/main/utils/json_converter.dart';
20+
import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/upload_notification_stat_request.dart';
2021
import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/upload_stat_request.dart';
2122
import 'package:sendbird_chat_sdk/src/internal/network/websocket/event/login_event.dart';
2223
import 'package:sendbird_chat_sdk/src/public/main/define/exceptions.dart';
@@ -241,19 +242,55 @@ class StatManager {
241242
await _clearDisallowedStats(); // Defensive code
242243

243244
final deviceId = await defaultStatPrefs.deviceId;
245+
244246
final dailyRecordStats = (await dailyRecordStatPrefs.uploadCandidateStats)
245247
.take(_maxStatCountPerRequest)
246248
.toList();
249+
247250
final copiedStats = cachedDefaultStats
248251
.take(_maxStatCountPerRequest - dailyRecordStats.length)
249252
.toList();
250253

254+
final List<NotificationStat> notificationStats = [];
255+
final List<DefaultStat> otherStats = [];
256+
final List<DefaultStat> remainingDefaultStats = [...cachedDefaultStats];
257+
258+
for (DefaultStat stat in copiedStats) {
259+
if (stat is NotificationStat) {
260+
notificationStats.add(stat);
261+
} else {
262+
otherStats.add(stat);
263+
}
264+
}
265+
251266
Object? exception;
267+
bool wereNotificationStatsSent = false;
252268
try {
253-
final stats = [...dailyRecordStats, ...copiedStats];
254-
await _chat.apiClient.send(
255-
UploadStatRequest(_chat, deviceId: deviceId, stats: stats),
256-
);
269+
if (notificationStats.isNotEmpty) {
270+
// Send notificationStats
271+
await _chat.apiClient.send(
272+
UploadNotificationStatRequest(_chat,
273+
deviceId: deviceId, stats: notificationStats),
274+
);
275+
276+
for (NotificationStat stat in notificationStats) {
277+
remainingDefaultStats.remove(stat);
278+
}
279+
280+
wereNotificationStatsSent = true;
281+
}
282+
283+
if (dailyRecordStats.isNotEmpty || otherStats.isNotEmpty) {
284+
// Send otherStats
285+
await _chat.apiClient.send(
286+
UploadStatRequest(_chat,
287+
deviceId: deviceId, stats: [...dailyRecordStats, ...otherStats]),
288+
);
289+
290+
for (DefaultStat stat in otherStats) {
291+
remainingDefaultStats.remove(stat);
292+
}
293+
}
257294
} catch (e) {
258295
if (copiedStats.length >= _minStatCount) {
259296
_minStatCount += _intervalCountToTryAgain;
@@ -271,20 +308,11 @@ class StatManager {
271308
if (exception == null) {
272309
_minStatCount = _initialMinStatCount;
273310

274-
final List<DefaultStat> remainingStats = [];
275-
try {
276-
remainingStats.addAll(cachedDefaultStats
277-
.sublist(copiedStats.length, cachedDefaultStats.length)
278-
.toList());
279-
} catch (e) {
280-
sbLog.d(StackTrace.current, 'e: ${e.toString()}');
281-
}
282-
283311
cachedDefaultStats.clear();
284-
cachedDefaultStats.addAll(remainingStats);
312+
cachedDefaultStats.addAll(remainingDefaultStats);
285313
await defaultStatPrefs
286314
.updateLastSentAt(DateTime.now().millisecondsSinceEpoch);
287-
await defaultStatPrefs.putStats(remainingStats);
315+
await defaultStatPrefs.putStats(remainingDefaultStats);
288316
await dailyRecordStatPrefs.remove(dailyRecordStats);
289317

290318
sbLog.d(
@@ -294,6 +322,22 @@ class StatManager {
294322
' cachedDefaultStats: ${cachedDefaultStats.length},'
295323
' defaultStatPrefs: ${await defaultStatPrefs.statCount},'
296324
' dailyRecordStatPrefs: ${(await dailyRecordStatPrefs.stats).length}');
325+
} else if (wereNotificationStatsSent) {
326+
_minStatCount = _initialMinStatCount;
327+
328+
cachedDefaultStats.clear();
329+
cachedDefaultStats.addAll(remainingDefaultStats);
330+
await defaultStatPrefs
331+
.updateLastSentAt(DateTime.now().millisecondsSinceEpoch);
332+
await defaultStatPrefs.putStats(remainingDefaultStats);
333+
334+
sbLog.d(
335+
StackTrace.current,
336+
'[StatTest][NotificationStatsSent] deviceId: $deviceId,'
337+
' pendingDefaultStats: ${pendingDefaultStats.length},'
338+
' cachedDefaultStats: ${cachedDefaultStats.length},'
339+
' defaultStatPrefs: ${await defaultStatPrefs.statCount},'
340+
' dailyRecordStatPrefs: ${(await dailyRecordStatPrefs.stats).length}');
297341
}
298342

299343
_isFlushing = false;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2023 Sendbird, Inc. All rights reserved.
2+
3+
import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart';
4+
import 'package:sendbird_chat_sdk/src/internal/main/stats/model/base_stat.dart';
5+
import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/http_client.dart';
6+
import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.dart';
7+
8+
class UploadNotificationStatRequest extends ApiRequest {
9+
@override
10+
HttpMethod get method => HttpMethod.post;
11+
12+
static const statUrl = 'sdk/notification_statistics';
13+
14+
UploadNotificationStatRequest(
15+
Chat chat, {
16+
required String deviceId,
17+
required List<BaseStat> stats,
18+
}) : super(chat: chat) {
19+
url = statUrl;
20+
body = {
21+
'device_id': deviceId,
22+
'log_entries': stats.map((stat) => stat.toJson()).toList(),
23+
};
24+
userId = null;
25+
}
26+
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: sendbird_chat_sdk
22
description: With Sendbird Chat for Flutter, you can easily build an in-app chat with all the essential messaging features.
3-
version: 4.2.24
3+
version: 4.2.25
44
homepage: https://sendbird.com
55
repository: https://github.com/sendbird/sendbird-chat-sdk-flutter
66
documentation: https://sendbird.com/docs/chat/sdk/v4/flutter/getting-started/send-first-message

0 commit comments

Comments
 (0)