Skip to content

Commit ba2d3bc

Browse files
Merge pull request #151 from sendbird/v4.7.0
Add 4.7.0.
2 parents ab009fc + 11bea88 commit ba2d3bc

30 files changed

+550
-60
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
## v4.7.0 (Oct 29, 2025)
2+
3+
### Features
4+
- Added `onConnectionDelayed(int retryAfter)` in `ConnectionHandler`
5+
- Automatically attempts to connect to the server in `retryAfter` seconds
6+
- Called when the server cannot handle the connection immediately when `SendbirdChat.connect()` is called
7+
- Called when the server is overloaded
8+
9+
### Improvements
10+
- Improved local caching
11+
112
## v4.6.0 (Oct 15, 2025)
213

314
### Features

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.6.0
53+
sendbird_chat_sdk: ^4.7.0
5454
```
5555
5656
- Run `flutter pub get` command in your project directory.

lib/src/internal/db/db.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,20 @@ class DB {
311311
);
312312
}
313313

314+
Future<BaseMessage?> getBaseMessageById(
315+
ChannelType channelType,
316+
String channelUrl,
317+
int messageId,
318+
) async {
319+
return await CChannelMessage.getBaseMessageById(
320+
_chat,
321+
_isar,
322+
channelType,
323+
channelUrl,
324+
messageId,
325+
);
326+
}
327+
314328
Future<List<BaseMessage>> getPendingMessages({
315329
required ChannelType channelType,
316330
required String channelUrl,

lib/src/internal/db/schema/message/meta/c_channel_message.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,31 @@ class CChannelMessage {
290290
return messages;
291291
}
292292

293+
static Future<BaseMessage?> getBaseMessageById(
294+
Chat chat,
295+
Isar isar,
296+
ChannelType channelType,
297+
String channelUrl,
298+
int messageId,
299+
) async {
300+
final cChannelMessages = await isar.cChannelMessages
301+
.where()
302+
.channelTypeChannelUrlEqualTo(channelType, channelUrl)
303+
.filter()
304+
.rootIdEqualTo(messageId.toString())
305+
.findAll();
306+
307+
List<BaseMessage> messages = [];
308+
for (final cChannelMessage in cChannelMessages) {
309+
final message = await cChannelMessage.toChannelMessage(chat, isar);
310+
if (message != null && message.message is BaseMessage) {
311+
messages.add(message.message as BaseMessage);
312+
}
313+
}
314+
315+
return messages.isNotEmpty ? messages[0] : null;
316+
}
317+
293318
static Future<List<BaseMessage>> getPendingMessages(
294319
Chat chat,
295320
Isar isar,

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ part 'chat_notifications.dart';
6666
part 'chat_push.dart';
6767
part 'chat_user.dart';
6868

69-
const sdkVersion = '4.6.0';
69+
const sdkVersion = '4.7.0';
7070

7171
// Internal implementation for main class. Do not directly access this class.
7272
class Chat with WidgetsBindingObserver {
@@ -244,11 +244,14 @@ class Chat with WidgetsBindingObserver {
244244
results.contains(ConnectivityResult.ethernet) ||
245245
results.contains(ConnectivityResult.vpn) ||
246246
results.contains(ConnectivityResult.other)) {
247-
if (SendbirdChat.currentUser != null) {
248-
if (chatContext.isChatConnected) {
247+
if (chatContext.isChatConnected) {
248+
if (SendbirdChat.currentUser != null ||
249+
chatContext.currentUserId != null) {
249250
sbLog.d(StackTrace.current, 'reconnect()');
250251
await connectionManager.reconnect(reset: true);
251-
} else if (chatContext.isFeedAuthenticated) {
252+
}
253+
} else if (chatContext.isFeedAuthenticated) {
254+
if (SendbirdChat.currentUser != null) {
252255
sbLog.d(StackTrace.current, 'refreshNotificationCollections()');
253256
collectionManager.refreshNotificationCollections();
254257
}

lib/src/internal/main/chat_context/chat_context.dart

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,19 @@ class ChatContext {
2424
ChatContext({
2525
required this.appId,
2626
required this.options,
27-
});
27+
}) {
28+
reconnectConfig = _getDefaultReconnectConfiguration(); // Check
29+
reconnectTask = ReconnectTask(_getDefaultReconnectConfiguration()); // Check
30+
}
31+
32+
ReconnectConfiguration _getDefaultReconnectConfiguration() {
33+
return ReconnectConfiguration(
34+
interval: 2,
35+
maxInterval: 20,
36+
multiplier: 2,
37+
maximumRetryCount: -1,
38+
); // Check
39+
}
2840

2941
factory ChatContext.test() =>
3042
ChatContext(appId: '', options: SendbirdChatOptions());
@@ -38,6 +50,7 @@ class ChatContext {
3850
// 'LOGI' event
3951
User? currentUser;
4052
String? currentUserId;
53+
String? nickname;
4154
List<Service> services = [];
4255
String? sessionKey;
4356
String? eKey;
@@ -50,6 +63,7 @@ class ChatContext {
5063
// Connection
5164
ReconnectTask? reconnectTask;
5265
Completer<User>? loginCompleter;
66+
String? connectingUrl;
5367

5468
// Entered OpenChannels
5569
Set<String> enteredOpenChannelUrls = {};
@@ -111,17 +125,19 @@ class ChatContext {
111125

112126
currentUser = null;
113127
currentUserId = null;
128+
nickname = null;
114129
services.clear();
115130
sessionKey = null;
116131
eKey = null;
117132
appInfo = null;
118133
uploadSizeLimit = 0;
119134
maxUnreadCountOnSuperGroup = null;
120135
lastConnectedAt = null;
121-
reconnectConfig = null;
136+
reconnectConfig = _getDefaultReconnectConfiguration(); // Check
122137

123138
loginCompleter = null;
124-
reconnectTask = null;
139+
reconnectTask = ReconnectTask(_getDefaultReconnectConfiguration()); // Check
140+
connectingUrl = null;
125141

126142
enteredOpenChannelUrls.clear();
127143

lib/src/internal/main/chat_manager/collection_manager/message_collection_manager.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,30 @@ extension MessageCollectionManager on CollectionManager {
682682
channelUrl: baseChannel.channelUrl,
683683
messages: addedMessages,
684684
);
685+
} else if (addedMessages.length == 1 &&
686+
addedMessages[0] is BaseMessage &&
687+
(addedMessages[0] as BaseMessage).previousMessageId != null &&
688+
(addedMessages[0] as BaseMessage).sendingStatus ==
689+
SendingStatus.succeeded) {
690+
final previousMessageId =
691+
(addedMessages[0] as BaseMessage).previousMessageId;
692+
if (previousMessageId != null) {
693+
if (messageCollection.removePreviousMessageIdForTest == true) {
694+
messageCollection.removePreviousMessageIdForTest = false;
695+
} else {
696+
final previousMessage = messageCollection.messageList
697+
.firstWhereOrNull((e) =>
698+
(e is BaseMessage && e.messageId == previousMessageId));
699+
700+
if (previousMessage != null) {
701+
// MessageChunk
702+
await _chat.dbManager.upsertMessagesInChunk(
703+
channelUrl: baseChannel.channelUrl,
704+
messages: [previousMessage, addedMessages[0]],
705+
);
706+
}
707+
}
708+
}
685709
}
686710
}
687711

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

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:sendbird_chat_sdk/src/internal/main/chat_context/chat_context.da
1111
import 'package:sendbird_chat_sdk/src/internal/main/chat_manager/collection_manager/collection_manager.dart';
1212
import 'package:sendbird_chat_sdk/src/internal/main/chat_manager/event_manager.dart';
1313
import 'package:sendbird_chat_sdk/src/internal/main/connection_state/connected_state.dart';
14+
import 'package:sendbird_chat_sdk/src/internal/main/connection_state/delayed_connecting_state.dart';
1415
import 'package:sendbird_chat_sdk/src/internal/main/logger/sendbird_logger.dart';
1516
import 'package:sendbird_chat_sdk/src/internal/main/model/delivery_status.dart';
1617
import 'package:sendbird_chat_sdk/src/internal/main/model/read_status.dart';
@@ -233,6 +234,8 @@ class CommandManager {
233234
await _processSessionRefresh(cmd);
234235
} else if (cmd.isLogin) {
235236
await _processLogin(cmd);
237+
} else if (cmd.isBusy) {
238+
await _processBusy(cmd);
236239
} else if (cmd.isSessionExpired) {
237240
await _processSessionExpired(cmd);
238241
} else if (cmd.isNewMessage) {
@@ -341,6 +344,7 @@ class CommandManager {
341344
_chat.chatContext
342345
..currentUser = event.user
343346
..currentUserId = event.user.userId
347+
..nickname = event.user.nickname
344348
..sessionKey = event.sessionKey
345349
..eKey = event.eKey
346350
..appInfo = event.appInfo
@@ -398,6 +402,70 @@ class CommandManager {
398402
}
399403
}
400404

405+
Future<void> _processBusy(Command cmd) async {
406+
int? retryAfter;
407+
int? reasonCode;
408+
String? message;
409+
410+
if (cmd.payload['retry_after'] != null &&
411+
cmd.payload['retry_after'] is int &&
412+
cmd.payload['retry_after'] >= 0) {
413+
retryAfter = cmd.payload['retry_after'] as int;
414+
415+
if (cmd.payload['reason_code'] != null &&
416+
cmd.payload['reason_code'] is int) {
417+
reasonCode = cmd.payload['reason_code'] as int;
418+
}
419+
420+
if (cmd.payload['message'] != null && cmd.payload['message'] is String) {
421+
message = cmd.payload['message'] as String;
422+
}
423+
424+
final e = ServerOverloadedException(
425+
message: message,
426+
data: {
427+
'retry_after': retryAfter,
428+
'reason_code': reasonCode,
429+
'message': message,
430+
},
431+
);
432+
433+
if (_chat.connectionManager.isConnecting()) {
434+
_chat.statManager.endWsConnectStat(
435+
hostUrl: _chat.chatContext.connectingUrl ?? '',
436+
success: false,
437+
errorCode: e.code,
438+
errorDescription: e.message,
439+
accumTrial: 1,
440+
);
441+
442+
if (_chat.chatContext.loginCompleter != null &&
443+
!_chat.chatContext.loginCompleter!.isCompleted) {
444+
_chat.chatContext.loginCompleter?.completeError(e);
445+
}
446+
} else if (_chat.connectionManager.isReconnecting()) {
447+
_chat.statManager.endWsConnectStat(
448+
hostUrl: _chat.chatContext.reconnectTask?.url ?? '',
449+
success: false,
450+
errorCode: e.code,
451+
errorDescription: e.message,
452+
accumTrial: _chat.chatContext.reconnectTask?.retryCount ?? 1,
453+
connectionId:
454+
_chat.chatContext.reconnectTask?.id ?? const Uuid().v1(),
455+
);
456+
} else if (_chat.connectionManager.isConnected()) {
457+
await _chat.connectionManager.disconnect(logout: false);
458+
}
459+
460+
_chat.connectionManager.changeState(DelayedConnectingState(
461+
chat: _chat,
462+
retryAfter: retryAfter,
463+
reasonCode: reasonCode,
464+
message: message,
465+
));
466+
}
467+
}
468+
401469
Future<void> _enterEnteredOpenChannels() async {
402470
final enteredOpenChannelUrlCopied = {
403471
..._chat.chatContext.enteredOpenChannelUrls
@@ -654,7 +722,7 @@ class CommandManager {
654722
}
655723
}
656724

657-
// only for open channel and broadcast channel
725+
// only for open channel and broadcast channel
658726
Future<void> _processMemberCountChange(Command cmd) async {
659727
final event = MCNTEvent.fromJsonWithChat(_chat, cmd.payload);
660728

0 commit comments

Comments
 (0)