Skip to content

Mirror ConnectionState onto core StreamConnectionState (breaking)#1739

Draft
aleksandar-apostolov wants to merge 2 commits into
gsd/phase-2-02-streamclient-rewritefrom
gsd/phase-2-03-connectionstate-rewrite
Draft

Mirror ConnectionState onto core StreamConnectionState (breaking)#1739
aleksandar-apostolov wants to merge 2 commits into
gsd/phase-2-02-streamclient-rewritefrom
gsd/phase-2-03-connectionstate-rewrite

Conversation

@aleksandar-apostolov

@aleksandar-apostolov aleksandar-apostolov commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Goal

Closes AND-1283

⚠ Breaking public API change. Replaces ConnectionState with a new sealed hierarchy that matches stream-android-core's StreamConnectionState shape 1:1. This removes the translation layer between core's connection state and ours — from now on we expose whatever core exposes.

Depends on #1738 (StreamClient wiring). Merge after parent.

Merge order: adapters (#1737) → StreamClient wiring (#1738) → this PR → merge to develop-v2.

Migration for integrators

Old variant Replacement
ConnectionState.PreConnect ConnectionState.Idle
ConnectionState.Loading ConnectionState.Connecting.Opening(userId) (or .Authenticating)
ConnectionState.Reconnecting Observe Disconnected(cause) followed by Connecting.* — core drives the reconnect flow
ConnectionState.Failed(error) ConnectionState.Disconnected(cause = error.toThrowable())
ConnectionState.Disconnected (data object) ConnectionState.Disconnected(cause = null) (now a data class)

Implementation

New sealed hierarchy:

public sealed interface ConnectionState {
    public data object Idle : ConnectionState
    public data class Disconnected(val cause: Throwable? = null) : ConnectionState
    public data class Connected(
        val connectedUser: StreamConnectedUser,
        val connectionId: String,
    ) : ConnectionState
    public sealed interface Connecting : ConnectionState {
        public data class Opening(val userId: String) : Connecting
        public data class Authenticating(val userId: String) : Connecting
    }
}
  • ClientState.handleError(error: Error) is removed — errors surface via Disconnected.cause instead.
  • ClientState.handleState(VideoSocketState) replaced with handleStreamState(StreamConnectionState).
  • StreamVideoClient's connection-state routing now goes end-to-end through core — closing the shim left by the parent PR.
  • ConnectionStateMapper finalized: bijective StreamConnectionState → ConnectionState conversion with full unit-test coverage.
  • Dead-code cleanup in ClientState.handleEvent: the redundant _connection.value = Connected assignment in the ConnectedEvent branch is removed. StreamClient drives connection state end-to-end now.

Consumer sweep.
A repo-wide grep for the removed variants outside legacy paths returned zero hits after fixes. All internal consumers were rewritten to the new shape — no deferred cleanups elsewhere in the SDK. Three test files needed one- to two-line adjustments (ClientAndAuthTest, RtcSessionTest, AndroidDeviceTest).

External integrators who read ConnectionState.Reconnecting or ConnectionState.Failed(error) in their apps will need to migrate per the table above.

Testing

  • Whole-project compile passes (all consumer modules build against the new sealed hierarchy).
  • Full stream-video-android-core:testDebugUnitTest suite green in ~2m30s. Includes new ClientStateTest and rewritten ClientStateConnectionMappingTest. @Ignore count unchanged at 21.
  • spotlessCheck green.
  • apiCheck green after apiDump regen.

Manual verification pending before merge:

  • Demo-app login → coordinator connect → join → leave for a regular user.
  • Same flow for a guest user — confirms the new GuestStreamTokenProvider path works end-to-end.
  • adb logcat clean of FATAL, ClassCastException, UnsatisfiedLinkError, and noise-cancellation errors during the smoke.

BCV .api diff

The diff below is intentional and limited to ConnectionState-family symbols. No unrelated public symbols changed.

diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api
index 5475daa3d0..d9306593e2 100644
--- a/stream-video-android-core/api/stream-video-android-core.api
+++ b/stream-video-android-core/api/stream-video-android-core.api
@@ -8886,7 +8886,6 @@ public final class io/getstream/video/android/core/ClientState {
 	public final fun getRingingCall ()Lkotlinx/coroutines/flow/StateFlow;
 	public final fun getTelecomIntegrationType ()Lio/getstream/video/android/core/notifications/internal/telecom/TelecomIntegrationType;
 	public final fun getUser ()Lkotlinx/coroutines/flow/StateFlow;
-	public final fun handleError (Lio/getstream/result/Error;)V
 	public final fun handleEvent (Lio/getstream/android/video/generated/models/VideoEvent;)V
 	public final fun hasActiveOrRingingCall ()Z
 	public final fun removeActiveCall ()V
@@ -8899,40 +8898,58 @@ public abstract interface class io/getstream/video/android/core/ConnectionState
 }
 
 public final class io/getstream/video/android/core/ConnectionState$Connected : io/getstream/video/android/core/ConnectionState {
-	public static final field INSTANCE Lio/getstream/video/android/core/ConnectionState$Connected;
+	public fun <init> (Lio/getstream/android/core/api/model/connection/StreamConnectedUser;Ljava/lang/String;)V
+	public final fun component1 ()Lio/getstream/android/core/api/model/connection/StreamConnectedUser;
+	public final fun component2 ()Ljava/lang/String;
+	public final fun copy (Lio/getstream/android/core/api/model/connection/StreamConnectedUser;Ljava/lang/String;)Lio/getstream/video/android/core/ConnectionState$Connected;
+	public static synthetic fun copy$default (Lio/getstream/video/android/core/ConnectionState$Connected;Lio/getstream/android/core/api/model/connection/StreamConnectedUser;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/video/android/core/ConnectionState$Connected;
 	public fun equals (Ljava/lang/Object;)Z
+	public final fun getConnectedUser ()Lio/getstream/android/core/api/model/connection/StreamConnectedUser;
+	public final fun getConnectionId ()Ljava/lang/String;
 	public fun hashCode ()I
 	public fun toString ()Ljava/lang/String;
 }
 
-public final class io/getstream/video/android/core/ConnectionState$Disconnected : io/getstream/video/android/core/ConnectionState {
-	public static final field INSTANCE Lio/getstream/video/android/core/ConnectionState$Disconnected;
+public abstract interface class io/getstream/video/android/core/ConnectionState$Connecting : io/getstream/video/android/core/ConnectionState {
+}
+
+public final class io/getstream/video/android/core/ConnectionState$Connecting$Authenticating : io/getstream/video/android/core/ConnectionState$Connecting {
+	public fun <init> (Ljava/lang/String;)V
+	public final fun component1 ()Ljava/lang/String;
+	public final fun copy (Ljava/lang/String;)Lio/getstream/video/android/core/ConnectionState$Connecting$Authenticating;
+	public static synthetic fun copy$default (Lio/getstream/video/android/core/ConnectionState$Connecting$Authenticating;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/video/android/core/ConnectionState$Connecting$Authenticating;
 	public fun equals (Ljava/lang/Object;)Z
+	public final fun getUserId ()Ljava/lang/String;
 	public fun hashCode ()I
 	public fun toString ()Ljava/lang/String;
 }
 
-public final class io/getstream/video/android/core/ConnectionState$Failed : io/getstream/video/android/core/ConnectionState {
-	public fun <init> (Lio/getstream/result/Error;)V
-	public final fun getError ()Lio/getstream/result/Error;
-}
-
-public final class io/getstream/video/android/core/ConnectionState$Loading : io/getstream/video/android/core/ConnectionState {
-	public static final field INSTANCE Lio/getstream/video/android/core/ConnectionState$Loading;
+public final class io/getstream/video/android/core/ConnectionState$Connecting$Opening : io/getstream/video/android/core/ConnectionState$Connecting {
+	public fun <init> (Ljava/lang/String;)V
+	public final fun component1 ()Ljava/lang/String;
+	public final fun copy (Ljava/lang/String;)Lio/getstream/video/android/core/ConnectionState$Connecting$Opening;
+	public static synthetic fun copy$default (Lio/getstream/video/android/core/ConnectionState$Connecting$Opening;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/video/android/core/ConnectionState$Connecting$Opening;
 	public fun equals (Ljava/lang/Object;)Z
+	public final fun getUserId ()Ljava/lang/String;
 	public fun hashCode ()I
 	public fun toString ()Ljava/lang/String;
 }
 
-public final class io/getstream/video/android/core/ConnectionState$PreConnect : io/getstream/video/android/core/ConnectionState {
-	public static final field INSTANCE Lio/getstream/video/android/core/ConnectionState$PreConnect;
+public final class io/getstream/video/android/core/ConnectionState$Disconnected : io/getstream/video/android/core/ConnectionState {
+	public fun <init> ()V
+	public fun <init> (Ljava/lang/Throwable;)V
+	public synthetic fun <init> (Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+	public final fun component1 ()Ljava/lang/Throwable;
+	public final fun copy (Ljava/lang/Throwable;)Lio/getstream/video/android/core/ConnectionState$Disconnected;
+	public static synthetic fun copy$default (Lio/getstream/video/android/core/ConnectionState$Disconnected;Ljava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/video/android/core/ConnectionState$Disconnected;
 	public fun equals (Ljava/lang/Object;)Z
+	public final fun getCause ()Ljava/lang/Throwable;
 	public fun hashCode ()I
 	public fun toString ()Ljava/lang/String;
 }
 
-public final class io/getstream/video/android/core/ConnectionState$Reconnecting : io/getstream/video/android/core/ConnectionState {
-	public static final field INSTANCE Lio/getstream/video/android/core/ConnectionState$Reconnecting;
+public final class io/getstream/video/android/core/ConnectionState$Idle : io/getstream/video/android/core/ConnectionState {
+	public static final field INSTANCE Lio/getstream/video/android/core/ConnectionState$Idle;
 	public fun equals (Ljava/lang/Object;)Z
 	public fun hashCode ()I
 	public fun toString ()Ljava/lang/String;

@aleksandar-apostolov aleksandar-apostolov added the pr:breaking-change API-breaking or behavioral change label Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled, or the PR is bot-authored.
  • An issue is linked (Linear ticket or GitHub issue), or the PR is bot-authored.

🎉 Great job! This PR is ready for review.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

SDK Size Comparison 📏

SDK Before After Difference Status
stream-video-android-core 12.05 MB 12.35 MB 0.30 MB 🟡
stream-video-android-ui-xml 5.68 MB 5.73 MB 0.05 MB 🟢
stream-video-android-ui-compose 6.28 MB 5.84 MB -0.44 MB 🚀

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gsd/phase-2-03-connectionstate-rewrite

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@aleksandar-apostolov aleksandar-apostolov force-pushed the gsd/phase-2-03-connectionstate-rewrite branch 2 times, most recently from eda7d6c to 5b4832d Compare July 3, 2026 07:38
@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown

Rewrite the public ConnectionState sealed interface to a five-variant
1:1 mirror of core's StreamConnectionState:
  - Idle (replaces PreConnect)
  - Connecting.Opening(userId), Connecting.Authenticating(userId)
  - Connected(connectedUser, connectionId) — was data object
  - Disconnected(cause: Throwable?) — absorbs Failed + Reconnecting

Add ClientState.handleStreamState(StreamConnectionState) and delete
ClientState.handleError(Error). Close the Plan 02-02 TODO(02-03) shim
in StreamVideoClient by wiring streamClientListener.onState directly
to handleStreamState. Update ConnectionStateMapper to the final
five-branch mapping (replacing the Plan 02-01 placeholder). Add
ClientStateTest covering every branch of handleStreamState. Update
consumer tests (ClientAndAuthTest, RtcSessionTest, AndroidDeviceTest)
to the new Connected/Idle payload shapes.
Reflects the new ConnectionState sealed interface (Idle,
Connecting.Opening, Connecting.Authenticating, Connected, Disconnected)
mirroring core's StreamConnectionState. Removed: PreConnect, Loading,
Reconnecting, Failed, ClientState.handleError. Added:
Connected(user, connectionId), Disconnected(cause) and Connecting
subtype payloads.
@aleksandar-apostolov aleksandar-apostolov force-pushed the gsd/phase-2-02-streamclient-rewrite branch from 092085e to a632706 Compare July 7, 2026 09:32
@aleksandar-apostolov aleksandar-apostolov force-pushed the gsd/phase-2-03-connectionstate-rewrite branch from 5b4832d to f5c9138 Compare July 7, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:breaking-change API-breaking or behavioral change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant