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 from 33c2ece to eda7d6c Compare July 2, 2026 13:22
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-03-connectionstate-rewrite branch from eda7d6c to 5b4832d Compare July 3, 2026 07:38
@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown

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