Skip to content

Commit c306582

Browse files
committed
fix(llc, ui): update message state on failure
This commit addresses the following: - **LLC:** Ensures that the message state is correctly updated to `failed` before attempting to retry in `sendMessage`, `updateMessage`, `partialUpdateMessage`, and `deleteMessage`. Previously, the state was only updated after a successful retry or if the retry failed again. - **LLC:** `retryFailedMessages` now correctly handles the case where there are no failed messages. - **UI:** The `message_actions_builder` now correctly hides actions for messages that are in a `deleted` state, even if they also have a `failed` state. - **UI:** Fixes an issue where deleted messages with a failed state would have incorrect spacing.
1 parent 3ad5cab commit c306582

File tree

3 files changed

+80
-84
lines changed

3 files changed

+80
-84
lines changed

packages/stream_chat/lib/src/client/channel.dart

Lines changed: 65 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ class Channel {
665665
_checkInitialized();
666666

667667
// Clean up stale error messages before sending a new message.
668-
state!.cleanUpStaleErrorMessages();
668+
state?.cleanUpStaleErrorMessages();
669669

670670
// Cancelling previous completer in case it's called again in the process
671671
// Eg. Updating the message while the previous call is in progress.
@@ -690,7 +690,7 @@ class Channel {
690690
).toList(),
691691
);
692692

693-
state!.updateMessage(message);
693+
state?.updateMessage(message);
694694

695695
try {
696696
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
@@ -724,20 +724,22 @@ class Channel {
724724
state: MessageState.sent,
725725
);
726726

727-
state!.updateMessage(sentMessage);
727+
state?.updateMessage(sentMessage);
728728

729729
return response;
730730
} catch (e) {
731+
final failedMessage = message.copyWith(
732+
// Update the message state to failed.
733+
state: MessageState.sendingFailed(
734+
skipPush: skipPush,
735+
skipEnrichUrl: skipEnrichUrl,
736+
),
737+
);
738+
739+
state?.updateMessage(failedMessage);
740+
// If the error is retriable, add it to the retry queue.
731741
if (e is StreamChatNetworkError && e.isRetriable) {
732-
state!._retryQueue.add([
733-
message.copyWith(
734-
// Update the message state to failed.
735-
state: MessageState.sendingFailed(
736-
skipPush: skipPush,
737-
skipEnrichUrl: skipEnrichUrl,
738-
),
739-
),
740-
]);
742+
state?._retryQueue.add([failedMessage]);
741743
}
742744

743745
rethrow;
@@ -756,7 +758,6 @@ class Channel {
756758
bool skipEnrichUrl = false,
757759
}) async {
758760
_checkInitialized();
759-
final originalMessage = message;
760761

761762
// Cancelling previous completer in case it's called again in the process
762763
// Eg. Updating the message while the previous call is in progress.
@@ -813,28 +814,20 @@ class Channel {
813814

814815
return response;
815816
} catch (e) {
816-
if (e is StreamChatNetworkError) {
817-
if (e.isRetriable) {
818-
state!._retryQueue.add([
819-
message.copyWith(
820-
// Update the message state to failed.
821-
state: MessageState.updatingFailed(
822-
skipPush: skipPush,
823-
skipEnrichUrl: skipEnrichUrl,
824-
),
825-
),
826-
]);
827-
} else {
828-
// Reset the message to original state if the update fails and is not
829-
// retriable.
830-
state?.updateMessage(originalMessage.copyWith(
831-
state: MessageState.updatingFailed(
832-
skipPush: skipPush,
833-
skipEnrichUrl: skipEnrichUrl,
834-
),
835-
));
836-
}
817+
final failedMessage = message.copyWith(
818+
// Update the message state to failed.
819+
state: MessageState.updatingFailed(
820+
skipPush: skipPush,
821+
skipEnrichUrl: skipEnrichUrl,
822+
),
823+
);
824+
825+
state?.updateMessage(failedMessage);
826+
// If the error is retriable, add it to the retry queue.
827+
if (e is StreamChatNetworkError && e.isRetriable) {
828+
state?._retryQueue.add([failedMessage]);
837829
}
830+
838831
rethrow;
839832
}
840833
}
@@ -851,7 +844,6 @@ class Channel {
851844
bool skipEnrichUrl = false,
852845
}) async {
853846
_checkInitialized();
854-
final originalMessage = message;
855847

856848
// Cancelling previous completer in case it's called again in the process
857849
// Eg. Updating the message while the previous call is in progress.
@@ -889,31 +881,19 @@ class Channel {
889881

890882
return response;
891883
} catch (e) {
892-
if (e is StreamChatNetworkError) {
893-
if (e.isRetriable) {
894-
state!._retryQueue.add([
895-
message.copyWith(
896-
// Update the message state to failed.
897-
state: MessageState.partialUpdatingFailed(
898-
set: set,
899-
unset: unset,
900-
skipEnrichUrl: skipEnrichUrl,
901-
),
902-
),
903-
]);
904-
} else {
905-
// Reset the message to original state if the update fails and is not
906-
// retriable.
907-
state?.updateMessage(
908-
originalMessage.copyWith(
909-
state: MessageState.partialUpdatingFailed(
910-
set: set,
911-
unset: unset,
912-
skipEnrichUrl: skipEnrichUrl,
913-
),
914-
),
915-
);
916-
}
884+
final failedMessage = message.copyWith(
885+
// Update the message state to failed.
886+
state: MessageState.partialUpdatingFailed(
887+
set: set,
888+
unset: unset,
889+
skipEnrichUrl: skipEnrichUrl,
890+
),
891+
);
892+
893+
state?.updateMessage(failedMessage);
894+
// If the error is retriable, add it to the retry queue.
895+
if (e is StreamChatNetworkError && e.isRetriable) {
896+
state?._retryQueue.add([failedMessage]);
917897
}
918898

919899
rethrow;
@@ -932,7 +912,7 @@ class Channel {
932912
// Directly deleting the local messages and bounced error messages as they
933913
// are not available on the server.
934914
if (message.remoteCreatedAt == null || message.isBouncedWithError) {
935-
state!.deleteMessage(
915+
state?.deleteMessage(
936916
message.copyWith(
937917
type: MessageType.deleted,
938918
localDeletedAt: DateTime.now(),
@@ -987,14 +967,17 @@ class Channel {
987967

988968
return response;
989969
} catch (e) {
970+
final failedMessage = message.copyWith(
971+
// Update the message state to failed.
972+
state: MessageState.deletingFailed(hard: hard),
973+
);
974+
975+
state?.updateMessage(failedMessage);
976+
// If the error is retriable, add it to the retry queue.
990977
if (e is StreamChatNetworkError && e.isRetriable) {
991-
state!._retryQueue.add([
992-
message.copyWith(
993-
// Update the message state to failed.
994-
state: MessageState.deletingFailed(hard: hard),
995-
),
996-
]);
978+
state?._retryQueue.add([failedMessage]);
997979
}
980+
998981
rethrow;
999982
}
1000983
}
@@ -1005,6 +988,9 @@ class Channel {
1005988
/// retry action:
1006989
/// - For [MessageState.sendingFailed], it attempts to send the message.
1007990
/// - For [MessageState.updatingFailed], it attempts to update the message.
991+
/// - For [MessageState.partialUpdatingFailed], it attempts to partially
992+
/// update the message with the same 'set' and 'unset' parameters that were
993+
/// used in the original request.
1008994
/// - For [MessageState.deletingFailed], it attempts to delete the message.
1009995
/// with the same 'hard' parameter that was used in the original request
1010996
/// - For messages with [isBouncedWithError], it attempts to send the message.
@@ -1026,13 +1012,14 @@ class Channel {
10261012
skipPush: skipPush,
10271013
skipEnrichUrl: skipEnrichUrl,
10281014
),
1029-
partialUpdatingFailed: (set, unset, skipEnrichUrl) =>
1030-
partialUpdateMessage(
1031-
message,
1032-
set: set,
1033-
unset: unset,
1034-
skipEnrichUrl: skipEnrichUrl,
1035-
),
1015+
partialUpdatingFailed: (set, unset, skipEnrichUrl) {
1016+
return partialUpdateMessage(
1017+
message,
1018+
set: set,
1019+
unset: unset,
1020+
skipEnrichUrl: skipEnrichUrl,
1021+
);
1022+
},
10361023
deletingFailed: (hard) => deleteMessage(message, hard: hard),
10371024
),
10381025
orElse: () {
@@ -2516,8 +2503,10 @@ class ChannelClientState {
25162503

25172504
/// Retry failed message.
25182505
Future<void> retryFailedMessages() async {
2519-
final failedMessages = [...messages, ...threads.values.expand((v) => v)]
2520-
.where((it) => it.state.isFailed);
2506+
final allMessages = [...messages, ...threads.values.flattened];
2507+
final failedMessages = allMessages.where((it) => it.state.isFailed);
2508+
2509+
if (failedMessages.isEmpty) return;
25212510
_retryQueue.add(failedMessages);
25222511
}
25232512

packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ class StreamMessageActionsBuilder {
6161
OwnUser? currentUser,
6262
Iterable<StreamMessageAction>? customActions,
6363
}) {
64+
final messageState = message.state;
65+
6466
// If the message is deleted, we don't show any actions.
65-
if (message.isDeleted) return [];
67+
if (messageState.isDeleted) return [];
6668

67-
final messageState = message.state;
6869
if (messageState.isFailed) {
6970
return [
7071
if (messageState.isSendingFailed || messageState.isUpdatingFailed) ...[

packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -342,12 +342,18 @@ class MessageWidgetContent extends StatelessWidget {
342342
}
343343

344344
Widget _buildMessageCard(BuildContext context) {
345-
if (message.isDeleted && !isFailedState) {
346-
return StreamDeletedMessage(
347-
borderRadiusGeometry: borderRadiusGeometry,
348-
borderSide: borderSide,
349-
shape: shape,
350-
messageTheme: messageTheme,
345+
if (message.isDeleted) {
346+
return Container(
347+
margin: EdgeInsetsDirectional.only(
348+
end: reverse && isFailedState ? 12.0 : 0.0,
349+
start: !reverse && isFailedState ? 12.0 : 0.0,
350+
),
351+
child: StreamDeletedMessage(
352+
borderRadiusGeometry: borderRadiusGeometry,
353+
borderSide: borderSide,
354+
shape: shape,
355+
messageTheme: messageTheme,
356+
),
351357
);
352358
}
353359

0 commit comments

Comments
 (0)