diff --git a/CHANGELOG.md b/CHANGELOG.md index 105550a..7dd7e18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v4.2.25 (Oct 21, 2024) + +### Improvements +- Fixed the reconnection bug while entering foreground +- Modified the endpoint of sbm metric + ## v4.2.24 (Oct 8, 2024) ### Improvements diff --git a/README.md b/README.md index b9ada9f..8fafb29 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Before installing Sendbird Chat SDK, you need to create a Sendbird application o ```yaml dependencies: - sendbird_chat_sdk: ^4.2.24 + sendbird_chat_sdk: ^4.2.25 ``` - Run `flutter pub get` command in your project directory. diff --git a/lib/src/internal/main/chat/chat.dart b/lib/src/internal/main/chat/chat.dart index aa81282..2fc9c1f 100644 --- a/lib/src/internal/main/chat/chat.dart +++ b/lib/src/internal/main/chat/chat.dart @@ -62,7 +62,7 @@ part 'chat_notifications.dart'; part 'chat_push.dart'; part 'chat_user.dart'; -const sdkVersion = '4.2.24'; +const sdkVersion = '4.2.25'; // Internal implementation for main class. Do not directly access this class. class Chat with WidgetsBindingObserver { @@ -91,6 +91,7 @@ class Chat with WidgetsBindingObserver { bool? _isObserverRegistered; int lastMarkAsReadTimestamp; + bool isBackground = false; // This allows a value of type T or T? to be treated as a value of type T?. // We use this so that APIs that have become non-nullable can still be used @@ -176,8 +177,13 @@ class Chat with WidgetsBindingObserver { Future didChangeAppLifecycleState(AppLifecycleState state) async { sbLog.i(StackTrace.current, 'state: $state'); - if (state == AppLifecycleState.paused) await _handleEnterBackground(); - if (state == AppLifecycleState.resumed) await _handleEnterForeground(); + if (state == AppLifecycleState.paused) { + isBackground = true; + await _handleEnterBackground(); + } else if (state == AppLifecycleState.resumed) { + isBackground = false; + await _handleEnterForeground(); + } } Future _handleEnterBackground() async { diff --git a/lib/src/internal/main/chat_manager/connection_manager.dart b/lib/src/internal/main/chat_manager/connection_manager.dart index fa49d09..86ab431 100644 --- a/lib/src/internal/main/chat_manager/connection_manager.dart +++ b/lib/src/internal/main/chat_manager/connection_manager.dart @@ -83,11 +83,11 @@ class ConnectionManager { ); } - Future disconnect({required logout}) async { + Future disconnect({required bool logout}) async { await _currentState.disconnect(logout: logout); } - Future reconnect({bool reset = false}) async { + Future reconnect({required bool reset}) async { return await _currentState.reconnect(reset: reset); } @@ -242,6 +242,7 @@ class ConnectionManager { Future doDisconnect({ required bool clear, bool logout = false, + bool fromEnterBackground = false, }) async { sbLog.i( StackTrace.current, @@ -262,7 +263,7 @@ class ConnectionManager { } } - await webSocketClient.close(); + final isClosedSuccessfully = await webSocketClient.close(); final disconnectedUserId = chat.chatContext.currentUserId ?? ''; @@ -294,10 +295,14 @@ class ConnectionManager { await chat.eventDispatcher.onDisconnected(); } - changeState(DisconnectedState(chat: chat)); + if (fromEnterBackground && !chat.isBackground && !isClosedSuccessfully) { + chat.connectionManager.reconnect(reset: true); // Check + } else { + changeState(DisconnectedState(chat: chat)); - if (clear && disconnectedUserId.isNotEmpty) { - chat.eventManager.notifyDisconnected(disconnectedUserId); + if (clear && disconnectedUserId.isNotEmpty) { + chat.eventManager.notifyDisconnected(disconnectedUserId); + } } } diff --git a/lib/src/internal/main/connection_state/base_connection_state.dart b/lib/src/internal/main/connection_state/base_connection_state.dart index e73502f..6916d29 100644 --- a/lib/src/internal/main/connection_state/base_connection_state.dart +++ b/lib/src/internal/main/connection_state/base_connection_state.dart @@ -15,8 +15,8 @@ abstract class BaseConnectionState { String? apiHost, String? wsHost, }); - Future disconnect({required logout}); - Future reconnect({bool reset = false}); + Future disconnect({required bool logout}); + Future reconnect({required bool reset}); Future enterBackground(); Future enterForeground(); diff --git a/lib/src/internal/main/connection_state/connected_state.dart b/lib/src/internal/main/connection_state/connected_state.dart index c4c3a90..31a87b0 100644 --- a/lib/src/internal/main/connection_state/connected_state.dart +++ b/lib/src/internal/main/connection_state/connected_state.dart @@ -37,7 +37,7 @@ class ConnectedState extends BaseConnectionState { } @override - Future disconnect({required logout}) async { + Future disconnect({required bool logout}) async { sbLog.i(StackTrace.current); await chat.connectionManager.doDisconnect(clear: logout, logout: logout); } @@ -51,7 +51,8 @@ class ConnectedState extends BaseConnectionState { @override Future enterBackground() async { sbLog.i(StackTrace.current); - await chat.connectionManager.doDisconnect(clear: false); + await chat.connectionManager + .doDisconnect(clear: false, fromEnterBackground: true); } @override diff --git a/lib/src/internal/main/connection_state/connecting_state.dart b/lib/src/internal/main/connection_state/connecting_state.dart index aaee3d4..ee89dc7 100644 --- a/lib/src/internal/main/connection_state/connecting_state.dart +++ b/lib/src/internal/main/connection_state/connecting_state.dart @@ -38,7 +38,7 @@ class ConnectingState extends BaseConnectionState { } @override - Future disconnect({required logout}) async { + Future disconnect({required bool logout}) async { sbLog.i(StackTrace.current); await chat.connectionManager.doDisconnect(clear: logout, logout: logout); } @@ -52,7 +52,8 @@ class ConnectingState extends BaseConnectionState { @override Future enterBackground() async { sbLog.i(StackTrace.current); - await chat.connectionManager.doDisconnect(clear: false); + await chat.connectionManager + .doDisconnect(clear: false, fromEnterBackground: true); } @override diff --git a/lib/src/internal/main/connection_state/disconnected_state.dart b/lib/src/internal/main/connection_state/disconnected_state.dart index 9b867b1..fd20e25 100644 --- a/lib/src/internal/main/connection_state/disconnected_state.dart +++ b/lib/src/internal/main/connection_state/disconnected_state.dart @@ -37,7 +37,7 @@ class DisconnectedState extends BaseConnectionState { } @override - Future disconnect({required logout}) async { + Future disconnect({required bool logout}) async { sbLog.i(StackTrace.current); await chat.connectionManager.doDisconnect(clear: logout, logout: logout); } @@ -51,7 +51,8 @@ class DisconnectedState extends BaseConnectionState { @override Future enterBackground() async { sbLog.i(StackTrace.current); - await chat.connectionManager.doDisconnect(clear: false); + await chat.connectionManager + .doDisconnect(clear: false, fromEnterBackground: true); } @override diff --git a/lib/src/internal/main/connection_state/reconnecting_state.dart b/lib/src/internal/main/connection_state/reconnecting_state.dart index c2ddda8..d68a6ca 100644 --- a/lib/src/internal/main/connection_state/reconnecting_state.dart +++ b/lib/src/internal/main/connection_state/reconnecting_state.dart @@ -36,7 +36,7 @@ class ReconnectingState extends BaseConnectionState { } @override - Future disconnect({required logout}) async { + Future disconnect({required bool logout}) async { sbLog.i(StackTrace.current); await chat.connectionManager.doDisconnect(clear: logout, logout: logout); } @@ -50,7 +50,8 @@ class ReconnectingState extends BaseConnectionState { @override Future enterBackground() async { sbLog.i(StackTrace.current); - await chat.connectionManager.doDisconnect(clear: false); + await chat.connectionManager + .doDisconnect(clear: false, fromEnterBackground: true); } @override diff --git a/lib/src/internal/main/stats/stat_manager.dart b/lib/src/internal/main/stats/stat_manager.dart index 11c1465..016cf9d 100644 --- a/lib/src/internal/main/stats/stat_manager.dart +++ b/lib/src/internal/main/stats/stat_manager.dart @@ -17,6 +17,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/stats/stat_state.dart'; import 'package:sendbird_chat_sdk/src/internal/main/stats/stat_type.dart'; import 'package:sendbird_chat_sdk/src/internal/main/stats/stat_utils.dart'; import 'package:sendbird_chat_sdk/src/internal/main/utils/json_converter.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/upload_notification_stat_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/main/upload_stat_request.dart'; import 'package:sendbird_chat_sdk/src/internal/network/websocket/event/login_event.dart'; import 'package:sendbird_chat_sdk/src/public/main/define/exceptions.dart'; @@ -241,19 +242,55 @@ class StatManager { await _clearDisallowedStats(); // Defensive code final deviceId = await defaultStatPrefs.deviceId; + final dailyRecordStats = (await dailyRecordStatPrefs.uploadCandidateStats) .take(_maxStatCountPerRequest) .toList(); + final copiedStats = cachedDefaultStats .take(_maxStatCountPerRequest - dailyRecordStats.length) .toList(); + final List notificationStats = []; + final List otherStats = []; + final List remainingDefaultStats = [...cachedDefaultStats]; + + for (DefaultStat stat in copiedStats) { + if (stat is NotificationStat) { + notificationStats.add(stat); + } else { + otherStats.add(stat); + } + } + Object? exception; + bool wereNotificationStatsSent = false; try { - final stats = [...dailyRecordStats, ...copiedStats]; - await _chat.apiClient.send( - UploadStatRequest(_chat, deviceId: deviceId, stats: stats), - ); + if (notificationStats.isNotEmpty) { + // Send notificationStats + await _chat.apiClient.send( + UploadNotificationStatRequest(_chat, + deviceId: deviceId, stats: notificationStats), + ); + + for (NotificationStat stat in notificationStats) { + remainingDefaultStats.remove(stat); + } + + wereNotificationStatsSent = true; + } + + if (dailyRecordStats.isNotEmpty || otherStats.isNotEmpty) { + // Send otherStats + await _chat.apiClient.send( + UploadStatRequest(_chat, + deviceId: deviceId, stats: [...dailyRecordStats, ...otherStats]), + ); + + for (DefaultStat stat in otherStats) { + remainingDefaultStats.remove(stat); + } + } } catch (e) { if (copiedStats.length >= _minStatCount) { _minStatCount += _intervalCountToTryAgain; @@ -271,20 +308,11 @@ class StatManager { if (exception == null) { _minStatCount = _initialMinStatCount; - final List remainingStats = []; - try { - remainingStats.addAll(cachedDefaultStats - .sublist(copiedStats.length, cachedDefaultStats.length) - .toList()); - } catch (e) { - sbLog.d(StackTrace.current, 'e: ${e.toString()}'); - } - cachedDefaultStats.clear(); - cachedDefaultStats.addAll(remainingStats); + cachedDefaultStats.addAll(remainingDefaultStats); await defaultStatPrefs .updateLastSentAt(DateTime.now().millisecondsSinceEpoch); - await defaultStatPrefs.putStats(remainingStats); + await defaultStatPrefs.putStats(remainingDefaultStats); await dailyRecordStatPrefs.remove(dailyRecordStats); sbLog.d( @@ -294,6 +322,22 @@ class StatManager { ' cachedDefaultStats: ${cachedDefaultStats.length},' ' defaultStatPrefs: ${await defaultStatPrefs.statCount},' ' dailyRecordStatPrefs: ${(await dailyRecordStatPrefs.stats).length}'); + } else if (wereNotificationStatsSent) { + _minStatCount = _initialMinStatCount; + + cachedDefaultStats.clear(); + cachedDefaultStats.addAll(remainingDefaultStats); + await defaultStatPrefs + .updateLastSentAt(DateTime.now().millisecondsSinceEpoch); + await defaultStatPrefs.putStats(remainingDefaultStats); + + sbLog.d( + StackTrace.current, + '[StatTest][NotificationStatsSent] deviceId: $deviceId,' + ' pendingDefaultStats: ${pendingDefaultStats.length},' + ' cachedDefaultStats: ${cachedDefaultStats.length},' + ' defaultStatPrefs: ${await defaultStatPrefs.statCount},' + ' dailyRecordStatPrefs: ${(await dailyRecordStatPrefs.stats).length}'); } _isFlushing = false; diff --git a/lib/src/internal/network/http/http_client/request/main/upload_notification_stat_request.dart b/lib/src/internal/network/http/http_client/request/main/upload_notification_stat_request.dart new file mode 100644 index 0000000..504729a --- /dev/null +++ b/lib/src/internal/network/http/http_client/request/main/upload_notification_stat_request.dart @@ -0,0 +1,26 @@ +// Copyright (c) 2023 Sendbird, Inc. All rights reserved. + +import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; +import 'package:sendbird_chat_sdk/src/internal/main/stats/model/base_stat.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/http_client.dart'; +import 'package:sendbird_chat_sdk/src/internal/network/http/http_client/request/api_request.dart'; + +class UploadNotificationStatRequest extends ApiRequest { + @override + HttpMethod get method => HttpMethod.post; + + static const statUrl = 'sdk/notification_statistics'; + + UploadNotificationStatRequest( + Chat chat, { + required String deviceId, + required List stats, + }) : super(chat: chat) { + url = statUrl; + body = { + 'device_id': deviceId, + 'log_entries': stats.map((stat) => stat.toJson()).toList(), + }; + userId = null; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 32f8d16..0bd448a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: sendbird_chat_sdk description: With Sendbird Chat for Flutter, you can easily build an in-app chat with all the essential messaging features. -version: 4.2.24 +version: 4.2.25 homepage: https://sendbird.com repository: https://github.com/sendbird/sendbird-chat-sdk-flutter documentation: https://sendbird.com/docs/chat/sdk/v4/flutter/getting-started/send-first-message