Skip to content

Commit e0e234c

Browse files
Change state update mechanism
1 parent 693328e commit e0e234c

File tree

5 files changed

+103
-11
lines changed

5 files changed

+103
-11
lines changed

stream-android-core/src/main/java/io/getstream/android/core/api/StreamClient.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import io.getstream.android.core.api.model.config.StreamHttpConfig
2525
import io.getstream.android.core.api.model.config.StreamSocketConfig
2626
import io.getstream.android.core.api.model.connection.StreamConnectedUser
2727
import io.getstream.android.core.api.model.connection.StreamConnectionState
28-
import io.getstream.android.core.api.model.connection.network.StreamNetworkInfo
28+
import io.getstream.android.core.api.model.connection.network.StreamNetworkState
2929
import io.getstream.android.core.api.model.value.StreamApiKey
3030
import io.getstream.android.core.api.model.value.StreamHttpClientInfoHeader
3131
import io.getstream.android.core.api.model.value.StreamUserId
@@ -117,7 +117,7 @@ public interface StreamClient {
117117
* - Hot & conflated: new collectors receive the latest value immediately.
118118
* - `null` if no network is available.
119119
*/
120-
@StreamInternalApi public val networkInfo: StateFlow<StreamNetworkInfo.Snapshot?>
120+
@StreamInternalApi public val networkState: StateFlow<StreamNetworkState>
121121

122122
/**
123123
* Establishes a connection for the current user.
@@ -291,6 +291,7 @@ public fun StreamClient(
291291
serialQueue = serialQueue,
292292
connectionIdHolder = connectionIdHolder,
293293
logger = clientLogger,
294+
mutableNetworkState = MutableStateFlow(StreamNetworkState.Unknown),
294295
mutableConnectionState = MutableStateFlow(StreamConnectionState.Idle),
295296
subscriptionManager = clientSubscriptionManager,
296297
networkMonitor = networkMonitor,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.getstream.android.core.api.model.connection.network
2+
3+
import io.getstream.android.core.annotations.StreamInternalApi
4+
5+
@StreamInternalApi
6+
public sealed class StreamNetworkState {
7+
8+
/**
9+
* Signals that the platform reported a permanent loss of network connectivity.
10+
*
11+
* This state mirrors the `ConnectivityManager.NetworkCallback.onUnavailable` callback, which
12+
* indicates no viable network path exists. Applications should back off from network work and
13+
* surface an offline UI until a different state is received.
14+
*
15+
* ### Example
16+
* ```kotlin
17+
* when (state) {
18+
* StreamNetworkState.Unavailable -> showOfflineBanner("No connection available")
19+
* else -> hideOfflineBanner()
20+
* }
21+
* ```
22+
*/
23+
public data object Unavailable : StreamNetworkState()
24+
25+
/**
26+
* Represents the initial, indeterminate state before any network callbacks have fired.
27+
*
28+
* Use this as a cue to defer UI decisions until more definitive information arrives. The state
29+
* will transition to one of the other variants once the monitor observes connectivity events.
30+
*/
31+
public data object Unknown : StreamNetworkState()
32+
33+
/**
34+
* Indicates that a network was previously tracked but has been lost.
35+
*
36+
* This corresponds to `ConnectivityManager.NetworkCallback.onLost`. Stream monitors emit this
37+
* when the active network disconnects but the system may still attempt reconnection, so you can
38+
* show transient offline messaging or pause network-heavy tasks.
39+
*/
40+
public data object Disconnected : StreamNetworkState()
41+
42+
/**
43+
* A network path is currently active and considered connected.
44+
*
45+
* This state maps to `ConnectivityManager.NetworkCallback.onAvailable` and carries the most
46+
* recent [StreamNetworkInfo.Snapshot], allowing callers to inspect transports, metering, or
47+
* other network characteristics before resuming work.
48+
*
49+
* ### Example
50+
* ```kotlin
51+
* when (state) {
52+
* is StreamNetworkState.Available ->
53+
* logger.i { "Connected via ${state.snapshot?.transports}" }
54+
* StreamNetworkState.Disconnected -> logger.w { "Network dropped" }
55+
* StreamNetworkState.Unavailable -> logger.e { "No connection" }
56+
* StreamNetworkState.Unknown -> logger.d { "Awaiting first update" }
57+
* }
58+
* ```
59+
*
60+
* @property snapshot Latest network snapshot, or `null` if collection failed.
61+
*/
62+
public data class Available(val snapshot: StreamNetworkInfo.Snapshot?) : StreamNetworkState()
63+
}

stream-android-core/src/main/java/io/getstream/android/core/api/socket/listeners/StreamClientListener.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package io.getstream.android.core.api.socket.listeners
1717

1818
import io.getstream.android.core.annotations.StreamInternalApi
1919
import io.getstream.android.core.api.model.connection.StreamConnectionState
20+
import io.getstream.android.core.api.model.connection.network.StreamNetworkInfo
21+
import io.getstream.android.core.api.model.connection.network.StreamNetworkState
2022

2123
/**
2224
* Listener interface for Feeds socket events.
@@ -46,4 +48,11 @@ public interface StreamClientListener {
4648
* @param err The error that occurred.
4749
*/
4850
public fun onError(err: Throwable) {}
51+
52+
/**
53+
* Called when the network connection changes.
54+
*
55+
* @param state The new network state.
56+
*/
57+
public fun onNetworkState(state: StreamNetworkState) {}
4958
}

stream-android-core/src/main/java/io/getstream/android/core/internal/client/StreamClientImpl.kt

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
*/
1616
package io.getstream.android.core.internal.client
1717

18-
import io.getstream.android.core.annotations.StreamInternalApi
1918
import io.getstream.android.core.api.StreamClient
2019
import io.getstream.android.core.api.authentication.StreamTokenManager
2120
import io.getstream.android.core.api.log.StreamLogger
2221
import io.getstream.android.core.api.model.StreamTypedKey.Companion.randomExecutionKey
2322
import io.getstream.android.core.api.model.connection.StreamConnectedUser
2423
import io.getstream.android.core.api.model.connection.StreamConnectionState
2524
import io.getstream.android.core.api.model.connection.network.StreamNetworkInfo
25+
import io.getstream.android.core.api.model.connection.network.StreamNetworkState
2626
import io.getstream.android.core.api.model.value.StreamUserId
2727
import io.getstream.android.core.api.observers.network.StreamNetworkMonitor
2828
import io.getstream.android.core.api.observers.network.StreamNetworkMonitorListener
@@ -48,6 +48,7 @@ internal class StreamClientImpl<T>(
4848
private val serialQueue: StreamSerialProcessingQueue,
4949
private val connectionIdHolder: StreamConnectionIdHolder,
5050
private val socketSession: StreamSocketSession<T>,
51+
private var mutableNetworkState: MutableStateFlow<StreamNetworkState>,
5152
private val mutableConnectionState: MutableStateFlow<StreamConnectionState>,
5253
private val logger: StreamLogger,
5354
private val subscriptionManager: StreamSubscriptionManager<StreamClientListener>,
@@ -64,12 +65,9 @@ internal class StreamClientImpl<T>(
6465
override val connectionState: StateFlow<StreamConnectionState>
6566
get() = mutableConnectionState.asStateFlow()
6667

67-
private var internalNetworkInfo: MutableStateFlow<StreamNetworkInfo.Snapshot?> =
68-
MutableStateFlow(null)
68+
override val networkState: StateFlow<StreamNetworkState>
69+
get() = mutableNetworkState.asStateFlow()
6970

70-
@StreamInternalApi
71-
override val networkInfo: StateFlow<StreamNetworkInfo.Snapshot?>
72-
get() = internalNetworkInfo.asStateFlow()
7371

7472
override fun subscribe(listener: StreamClientListener): Result<StreamSubscription> =
7573
subscriptionManager.subscribe(listener)
@@ -135,19 +133,34 @@ internal class StreamClientImpl<T>(
135133
logger.v {
136134
"[connect] Network connected: $snapshot"
137135
}
138-
internalNetworkInfo.update(snapshot)
136+
val state = StreamNetworkState.Available(snapshot)
137+
mutableNetworkState.update(state)
138+
subscriptionManager.forEach {
139+
it.onNetworkState(state)
140+
}
139141
}
140142

141143
override suspend fun onNetworkLost(permanent: Boolean) {
142144
logger.v { "[connect] Network lost" }
143-
internalNetworkInfo.update(null)
145+
val state = if (permanent) {
146+
StreamNetworkState.Unavailable
147+
} else {
148+
StreamNetworkState.Disconnected
149+
}
150+
mutableNetworkState.update(state)
151+
subscriptionManager.forEach {
152+
it.onNetworkState(state)
153+
}
144154
}
145155

146156
override suspend fun onNetworkPropertiesChanged(
147157
snapshot: StreamNetworkInfo.Snapshot
148158
) {
149159
logger.v { "[connect] Network changed: $snapshot" }
150-
internalNetworkInfo.update(snapshot)
160+
mutableNetworkState.update(StreamNetworkState.Available(snapshot))
161+
subscriptionManager.forEach {
162+
it.onNetworkState(StreamNetworkState.Available(snapshot))
163+
}
151164
}
152165
},
153166
StreamSubscriptionManager.Options(

stream-android-core/src/test/java/io/getstream/android/core/api/socket/listeners/StreamListenersDefaultImplsTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package io.getstream.android.core.api.socket.listeners
1919

2020
import io.getstream.android.core.annotations.StreamInternalApi
2121
import io.getstream.android.core.api.model.connection.StreamConnectionState
22+
import io.getstream.android.core.api.model.connection.network.StreamNetworkState
2223
import kotlin.test.assertEquals
2324
import kotlinx.coroutines.channels.Channel
2425
import kotlinx.coroutines.launch
@@ -50,6 +51,7 @@ internal class StreamListenersDefaultImplsTest {
5051
val stateChannel = Channel<StreamConnectionState>(capacity = 1)
5152
val eventChannel = Channel<Any>(capacity = 1)
5253
val errorChannel = Channel<Throwable>(capacity = 1)
54+
val networkChannel = Channel<StreamNetworkState>(capacity = 1)
5355

5456
val listener =
5557
object : StreamClientListener {
@@ -64,6 +66,10 @@ internal class StreamListenersDefaultImplsTest {
6466
override fun onError(err: Throwable) {
6567
errorChannel.trySend(err)
6668
}
69+
70+
override fun onNetworkState(state: StreamNetworkState) {
71+
networkChannel.trySend(state)
72+
}
6773
}
6874

6975
val state = StreamConnectionState.Connecting.Opening("user")

0 commit comments

Comments
 (0)