-
Notifications
You must be signed in to change notification settings - Fork 54
fix(samples): Fix deep linking in dogfooding app #1315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,8 @@ import 'dart:convert'; | |
| import 'package:crypto/crypto.dart'; | ||
| // �🐦 Flutter imports: | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:stream_chat_flutter/stream_chat_flutter.dart'; | ||
| import 'package:stream_chat_flutter/stream_chat_flutter.dart' | ||
| hide CurrentPlatform; | ||
| import 'package:stream_video_filters/video_effects_manager.dart'; | ||
| import 'package:stream_video_flutter/stream_video_flutter.dart' hide User; | ||
|
|
||
|
|
@@ -59,6 +60,7 @@ class _CallScreenState extends State<CallScreen> { | |
| static const _snackbarCooldown = Duration(seconds: 5); | ||
|
|
||
| Channel? _channel; | ||
| StreamSubscription<Event>? _chatConnectionRecoverySubscription; | ||
| ParticipantLayoutMode _currentLayoutMode = ParticipantLayoutMode.grid; | ||
| bool _moreMenuVisible = false; | ||
|
|
||
|
|
@@ -104,6 +106,7 @@ class _CallScreenState extends State<CallScreen> { | |
| _speakingWhileMutedDebounce?.cancel(); | ||
| _speakingWhileMutedSubscription.cancel(); | ||
| _speakingWhileMuted.dispose(); | ||
| _chatConnectionRecoverySubscription?.cancel(); | ||
| widget.call.leave(); | ||
| _userChatRepo.disconnectUser(); | ||
| _videoEffectsManager.dispose(); | ||
|
|
@@ -138,8 +141,25 @@ class _CallScreenState extends State<CallScreen> { | |
| appPreferences.environment, | ||
| ); | ||
|
|
||
| if (!mounted) return; | ||
|
|
||
| // A channel watch is bound to the chat websocket connection id, and | ||
| // StreamChatCore disables the client-level state recovery. After a | ||
| // reconnect (e.g. the network blip that also triggers a video fast | ||
| // reconnect) nothing re-watches the channel, so new messages silently stop | ||
| // reaching the device. Re-watch it ourselves. | ||
| _chatConnectionRecoverySubscription = _userChatRepo.chatClient | ||
| .on(EventType.connectionRecovered) | ||
| .listen((_) async { | ||
|
Comment on lines
+144
to
+153
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- changed file context ---'
sed -n '1,190p' dogfooding/lib/screens/call_screen.dart
printf '%s\n' '--- bound chat APIs and channel setup ---'
rg -n -C 4 'createChannel|connectionRecovered|chatClient\.on|chatClient|StreamChatCore|stateRecovery|watch\(' dogfooding/lib lib 2>/dev/null | head -n 300Repository: GetStream/stream-video-flutter Length of output: 12263 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- dependency versions ---'
rg -n -C 3 'stream_chat_flutter|stream_chat' pubspec.yaml pubspec.lock dogfooding/pubspec.yaml dogfooding/pubspec.lock 2>/dev/null || true
printf '%s\n' '--- local package sources, if present ---'
find . -path '*/stream_chat*' -o -path '*/stream_chat_flutter*' | head -n 100
printf '%s\n' '--- event stream declarations/usages ---'
rg -n -C 5 'Stream<Event>|Stream<.*Event|Stream<EventType|connectionRecovered|StreamChatClient.*on|\.on\(EventType' . --glob '*.dart' --glob '*.yaml' --glob '*.lock' | head -n 300Repository: GetStream/stream-video-flutter Length of output: 17715 🌐 Web query:
💡 Result: In the Stream Chat SDK (stream_chat) version 10.2.0, connectionRecovered is a constant within the EventType class, representing an event sent when the WebSocket connection has successfully recovered [1][2]. When the StreamChatClient detects that the connection has been restored, it triggers this event [1][3]. Starting in version 10.2.0, the client includes a recoverStateOnReconnect property (which defaults to true) [4][2]. When this property is enabled, the client automatically re-queries active channels upon reconnection [5][2]. If you set recoverStateOnReconnect to false—often done when managing state manually via list controllers—the client will no longer automatically refresh these channels [2][6]. In such cases, you are responsible for handling state recovery by listening to the connectionRecovered event and triggering the necessary refresh (e.g., calling channel.watch) [5][6]: final subscription = client.on(EventType.connectionRecovered).listen((_) { channel.watch; }); This pattern ensures that your application state remains synchronized with the server following a network interruption [5][6]. Citations:
🏁 Script executed: #!/bin/bash
set -e
rg -n -C 8 'StreamChatClient|recoverStateOnReconnect|StreamChatCore|chatClient' dogfooding/lib pubspec.yaml pubspec.lock --glob '*.dart' --glob '*.yaml' --glob '*.lock'Repository: GetStream/stream-video-flutter Length of output: 8749 🌐 Web query:
💡 Result: In the Stream Chat Flutter SDK, specifically starting from version 10.0.0, the recoverStateOnReconnect property on the StreamChatClient controls whether the client automatically attempts to recover its internal state upon reconnection [1][2]. When using StreamChatCore, this property is automatically set to false on initialization [1][2]. This is because the SDK's higher-level list controllers (such as channel list controllers) are designed to manage their own state recovery independently [2]. If you are watching a channel outside of these managed list controllers, you should handle state recovery manually by subscribing to the client's connectionRecovered event and triggering a watch on the specific channel [1]. You can implement this as follows: client.on(EventType.connectionRecovered).listen((event) { channel.watch; }); This change was introduced to prevent redundant data fetching and improve efficiency by avoiding client-level state recovery when individual components already handle their own data lifecycle [2]. Citations:
🌐 Web query:
💡 Result: In the Stream Chat Flutter SDK, the StreamChatClient does not typically use a method named on(EventType) directly to subscribe to events in the way suggested by your query [1][2]. Instead, event handling is generally managed through specific streams exposed by the client or its state, or through dedicated controllers [3][4]. If you are looking to listen to global client events (such as connection changes), you can subscribe to streams provided by the client [5][1]. For example, to react to connection recovery, you would listen to a specific event stream: final subscription = client.on(EventType.connectionRecovered).listen((event) { // Handle connection recovered }); Note that the availability of specific EventType members and the structure of these event streams can vary by SDK version [5][6]. The Stream Chat SDK provides a robust set of streams to monitor application state, user activities, and channel updates [3][4]. For most UI-related event handling, it is recommended to use the provided controllers (such as StreamChannelListController) which handle these subscriptions internally for you [4][7]. For the most accurate implementation details for your specific project, please refer to the official documentation and the migration guides available in the official GitHub repository [5][2]. Citations:
🏁 Script executed: #!/bin/bash
set -e
python3 - <<'PY'
import json
import urllib.request
base = "https://api.github.com/repos/GetStream/stream-chat-flutter"
for ref in ("v10.2.0", "10.2.0", "master"):
url = f"{base}/git/trees/{ref}?recursive=1"
try:
with urllib.request.urlopen(url) as response:
data = json.load(response)
print(f"REF {ref}")
for item in data.get("tree", []):
path = item.get("path", "")
if path.endswith(".dart") and any(
token in path.lower()
for token in ("client", "event", "core")
):
print(path)
break
except Exception as exc:
print(f"REF {ref}: {exc}")
PY
printf '%s\n' '--- versioned API documentation ---'
for url in \
'https://pub.dev/documentation/stream_chat/10.2.0/stream_chat/StreamChatClient/on.html' \
'https://pub.dev/documentation/stream_chat/10.2.0/stream_chat/StreamChatClient-class.html'; do
echo "URL $url"
curl -L --fail --silent "$url" | rg -o '.{0,160}(on\\(|connectionRecovered|recoverStateOnReconnect).{0,240}' | head -n 20 || true
doneRepository: GetStream/stream-video-flutter Length of output: 1083 🏁 Script executed: #!/bin/bash
set -e
archive='https://pub.dev/api/archives/stream_chat-10.2.0.tar.gz'
printf '%s\n' '--- relevant archive paths ---'
curl -k -L --fail --silent "$archive" \
| tar -tzf - \
| grep -Ei '(^|/)(stream_chat_client|event|connection|state).*.dart$' \
| head -n 100Repository: GetStream/stream-video-flutter Length of output: 746 🏁 Script executed: #!/bin/bash
set -e
archive='https://pub.dev/api/archives/stream_chat-10.2.0.tar.gz'
printf '%s\n' '--- client and event source paths ---'
curl -k -L --fail --silent "$archive" \
| tar -tzf - \
| grep -Ei '(^|/)(client|stream_chat|event|state).*\.dart$' \
| head -n 160
printf '%s\n' '--- event controller source ---'
curl -k -L --fail --silent "$archive" \
| tar -xOzf - lib/src/core/util/event_controller.dart \
| cat -n
printf '%s\n' '--- event resolver source ---'
curl -k -L --fail --silent "$archive" \
| tar -xOzf - lib/src/client/event_resolvers.dart \
| cat -nRepository: GetStream/stream-video-flutter Length of output: 10095 🏁 Script executed: #!/bin/bash
set -e
archive='https://pub.dev/api/archives/stream_chat-10.2.0.tar.gz'
curl -k -L --fail --silent "$archive" \
| tar -xOzf - lib/src/client/client.dart \
| rg -n -C 8 'EventController|Stream<| on\(|connectionRecovered|recoverStateOnReconnect|stateRecovery|add\('Repository: GetStream/stream-video-flutter Length of output: 16517 🏁 Script executed: #!/bin/bash
set -e
archive='https://pub.dev/api/archives/stream_chat-10.2.0.tar.gz'
curl -k -L --fail --silent "$archive" \
| tar -xOzf - lib/src/client/channel.dart \
| rg -n -C 10 'Future<.*watch|watch\(|connectionId|connection_id|subscribe|eventStream|_watch'Repository: GetStream/stream-video-flutter Length of output: 2688 🏁 Script executed: #!/bin/bash
set -e
archive='https://pub.dev/api/archives/stream_chat-10.2.0.tar.gz'
src=$(mktemp)
curl -k -L --fail --silent "$archive" | tar -xOzf - lib/src/client/channel.dart > "$src"
printf '%s\n' '--- channel query/watch path ---'
sed -n '1880,2100p' "$src"
printf '%s\n' '--- channel state subscription path ---'
sed -n '2385,2525p' "$src"
rm -f "$src"Repository: GetStream/stream-video-flutter Length of output: 11033 🏁 Script executed: #!/bin/bash
set -e
archive='https://pub.dev/api/archives/stream_chat-10.2.0.tar.gz'
printf '%s\n' '--- queryChannel implementation ---'
curl -k -L --fail --silent "$archive" \
| tar -xOzf - lib/src/client/client.dart \
| rg -n -C 12 'queryChannel\(|connectionId|connection_id'
printf '%s\n' '--- connection-id interceptor ---'
curl -k -L --fail --silent "$archive" \
| tar -xOzf - lib/src/core/http/interceptor/connection_id_interceptor.dart \
| cat -n
printf '%s\n' '--- connection-id manager ---'
curl -k -L --fail --silent "$archive" \
| tar -xOzf - lib/src/core/http/connection_id_manager.dart \
| cat -nRepository: GetStream/stream-video-flutter Length of output: 11416 Register recovery handling before the initial channel watch.
🤖 Prompt for AI Agents |
||
| try { | ||
| await _channel?.watch(); | ||
| } catch (e) { | ||
| debugPrint('Failed to re-watch chat channel after reconnect: $e'); | ||
| } | ||
| }); | ||
|
|
||
| // Rebuild the widget to enable the chat button. | ||
| if (mounted) setState(() {}); | ||
| setState(() {}); | ||
| } | ||
|
|
||
| void showParticipants(BuildContext context) { | ||
|
|
@@ -317,7 +337,10 @@ class _CallScreenState extends State<CallScreen> { | |
| AppColorPalette.appRed, | ||
| // Keep the track alive on mute so speaking-while- | ||
| // muted detection also works on iOS/macOS. | ||
| stopTrackOnMute: false, | ||
| stopTrackOnMute: | ||
| CurrentPlatform.isIos || CurrentPlatform.isMacOS | ||
| ? false | ||
| : null, | ||
| ), | ||
| ToggleCameraOption( | ||
| call: call, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: GetStream/stream-video-flutter
Length of output: 222
🏁 Script executed:
Repository: GetStream/stream-video-flutter
Length of output: 18006
🌐 Web query:
Dart Uri.replace path parameter percent encoding Uri.encodeComponent path documentation💡 Result:
In Dart, the Uri class handles percent-encoding automatically when you use its constructor or the replace method with specific arguments, which is the recommended approach to avoid manual encoding [1][2][3]. Using Uri.replace and Path Parameters The replace method allows you to update specific parts of a Uri instance [4]. When updating the path, you have two primary options: 1. path parameter: When you provide a string to the path argument, Dart treats it as a pre-constructed URI path [1][3]. While invalid characters are often escaped, it is not designed for individual segments containing special characters like forward slashes [1][3]. 2. pathSegments parameter: This is the preferred way to construct or modify paths [2]. When you pass an Iterable of strings to pathSegments, Dart automatically percent-encodes each segment individually and then joins them with forward slashes [1][3]. This prevents special characters within a segment from being misinterpreted as path delimiters [2][5]. Note on Absolute Paths: A known behavior when using pathSegments in replace is that it does not always preserve a leading slash if not explicitly handled [6]. If you need an absolute path, you can include an empty string as the first element in your pathSegments list (e.g., ['', 'segment1', 'segment2']) [6]. Uri.encodeComponent You should generally avoid using Uri.encodeComponent to manually build URI strings [2]. Uri.encodeComponent is designed to encode a string so it is safe for use as a single literal URI component (e.g., a single query parameter value) [2][5]. It encodes almost everything except unreserved characters, including forward slashes, which will break your URI structure if used on a full path [2][5]. Summary of Best Practices - Use pathSegments or queryParameters in the Uri constructor or replace method whenever possible; these methods handle all necessary encoding and decoding for you [1][2][3]. - Do not use Uri.encodeComponent for entire paths [5]. - If you must manually process path components, use pathSegments to maintain control over individual segments without needing to manually encode them [2].
Citations:
🏁 Script executed:
Repository: GetStream/stream-video-flutter
Length of output: 2352
Build the replacement URI from path segments.
Uri.pathSegmentsdecodesa%2Fbtoa/b. The currentpath:replacement can create/join/a/b, which does not matchjoin/:callIdand can cause recovery to selectainstead ofa/b.Use
pathSegments: ['', 'join', callId]. Do not passUri.encodeComponent(callId)throughpath:becausepath:accepts a complete URI path.🤖 Prompt for AI Agents