Skip to content

Commit b46b983

Browse files
authored
Disconnect on dispose option (#40)
* Disconnect on dispose option * spotless changeset * Update livekit android sdk to 2.16.0
1 parent 13f66c0 commit b46b983

6 files changed

Lines changed: 93 additions & 5 deletions

File tree

.changeset/beige-teachers-fetch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"components-android": minor
3+
---
4+
5+
Add disconnectOnDispose argument to RoomScope and rememberLiveKitRoom

.changeset/forty-spiders-run.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"components-android": patch
3+
---
4+
5+
Update livekit sdk to 2.16.0

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ local.properties
2121
node_modules/
2222
npm-debug.log
2323
yarn-debug.log
24-
yarn-error.log
24+
yarn-error.log
25+
runConfigurations.xml

livekit-compose-components/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ dokkaHtml {
8686
}
8787
}
8888

89-
var livekitVersion = "2.14.0"
89+
var livekitVersion = "2.16.0"
9090
dependencies {
9191
// For local development with the LiveKit Android SDK only.
9292
// api "io.livekit:livekit-android-sdk"

livekit-compose-components/src/main/java/io/livekit/android/compose/local/RoomLocal.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 LiveKit, Inc.
2+
* Copyright 2023-2025 LiveKit, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -104,6 +104,10 @@ private val DEFAULT_ERROR_HANDLER: ((Room, Exception?) -> Unit) = { _, e ->
104104
* @param onDisconnected a listener to be called upon room disconnection.
105105
* @param onError a listener to be called upon room error.
106106
* @param passedRoom if a [Room] is provided, it will be used. If null, a new Room will be created instead.
107+
* @param disconnectOnDispose by default, this composable handles the connection management and will disconnect the room
108+
* when the composable goes out of scope. Setting this to false will disable this behavior. This is effective in combination
109+
* with [passedRoom], when you need to keep the [Room] object alive and connected separately from the UI
110+
* (for example, with a background service).
107111
*/
108112
@Composable
109113
fun rememberLiveKitRoom(
@@ -119,6 +123,7 @@ fun rememberLiveKitRoom(
119123
onDisconnected: (suspend CoroutineScope.(Room) -> Unit)? = null,
120124
onError: ((Room, Exception?) -> Unit)? = DEFAULT_ERROR_HANDLER,
121125
passedRoom: Room? = null,
126+
disconnectOnDispose: Boolean = true,
122127
): Room {
123128
val context = LocalContext.current
124129
val room = remember(passedRoom) {
@@ -210,7 +215,7 @@ fun rememberLiveKitRoom(
210215

211216
DisposableEffect(room, connect) {
212217
onDispose {
213-
if (connect) {
218+
if (connect && disconnectOnDispose) {
214219
room.disconnect()
215220
}
216221
}
@@ -242,6 +247,10 @@ fun rememberLiveKitRoom(
242247
* @param onDisconnected a listener to be called upon room disconnection.
243248
* @param onError a listener to be called upon room error.
244249
* @param passedRoom if a [Room] is provided, it will be used. If null, a new Room will be created instead.
250+
* @param disconnectOnDispose by default, this composable handles the connection management and will disconnect the room
251+
* when the composable goes out of scope. Setting this to false will disable this behavior. This is effective in combination
252+
* with [passedRoom], when you need to keep the [Room] object alive and connected separately from the UI
253+
* (for example, with a background service).
245254
*/
246255
@Composable
247256
fun RoomScope(
@@ -257,6 +266,7 @@ fun RoomScope(
257266
onDisconnected: (suspend CoroutineScope.(Room) -> Unit)? = null,
258267
onError: ((Room, Exception?) -> Unit)? = null,
259268
passedRoom: Room? = null,
269+
disconnectOnDispose: Boolean = true,
260270
content: @Composable (room: Room) -> Unit
261271
) {
262272
val room = rememberLiveKitRoom(
@@ -271,7 +281,8 @@ fun RoomScope(
271281
onConnected = onConnected,
272282
onDisconnected = onDisconnected,
273283
onError = onError,
274-
passedRoom = passedRoom
284+
passedRoom = passedRoom,
285+
disconnectOnDispose = disconnectOnDispose,
275286
)
276287

277288
CompositionLocalProvider(

livekit-compose-components/src/test/java/io/livekit/android/compose/local/RoomScopeTest.kt

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@ package io.livekit.android.compose.local
1818

1919
import androidx.compose.runtime.Composable
2020
import androidx.compose.runtime.CompositionLocalProvider
21+
import androidx.compose.runtime.LaunchedEffect
22+
import androidx.compose.runtime.getValue
23+
import androidx.compose.runtime.mutableStateOf
24+
import androidx.compose.runtime.remember
25+
import androidx.compose.runtime.setValue
2126
import androidx.compose.ui.platform.LocalContext
2227
import app.cash.molecule.RecompositionMode
2328
import app.cash.molecule.moleculeFlow
2429
import app.cash.turbine.test
2530
import io.livekit.android.room.Room
2631
import io.livekit.android.test.MockE2ETest
2732
import kotlinx.coroutines.ExperimentalCoroutinesApi
33+
import kotlinx.coroutines.delay
34+
import org.junit.Assert.assertEquals
2835
import org.junit.Assert.assertNotNull
2936
import org.junit.Test
3037

@@ -62,6 +69,65 @@ class RoomScopeTest : MockE2ETest() {
6269
}
6370
}
6471

72+
@Test
73+
fun disconnectOnDispose() = runTest {
74+
// mock room needs connecting manually
75+
connect()
76+
moleculeFlow(RecompositionMode.Immediate) {
77+
var useScope by remember { mutableStateOf(true) }
78+
LaunchedEffect(Unit) {
79+
delay(1)
80+
useScope = false
81+
}
82+
if (useScope) {
83+
RoomScopeSetup {
84+
RoomScope(
85+
connect = true,
86+
disconnectOnDispose = true,
87+
passedRoom = room
88+
) {}
89+
}
90+
}
91+
1
92+
}.test {
93+
awaitItem()
94+
assertEquals(Room.State.CONNECTED, room.state)
95+
delay(1000)
96+
awaitItem()
97+
assertEquals(Room.State.DISCONNECTED, room.state)
98+
}
99+
}
100+
101+
@Test
102+
fun noDisconnectOnDispose() = runTest {
103+
// mock room needs connecting manually
104+
connect()
105+
moleculeFlow(RecompositionMode.Immediate) {
106+
var useScope by remember { mutableStateOf(true) }
107+
LaunchedEffect(Unit) {
108+
delay(1)
109+
useScope = false
110+
}
111+
if (useScope) {
112+
RoomScopeSetup {
113+
RoomScope(
114+
connect = true,
115+
disconnectOnDispose = false,
116+
passedRoom = room
117+
) {
118+
}
119+
}
120+
}
121+
1
122+
}.test {
123+
awaitItem()
124+
assertEquals(Room.State.CONNECTED, room.state)
125+
delay(1000)
126+
awaitItem()
127+
assertEquals(Room.State.CONNECTED, room.state)
128+
}
129+
}
130+
65131
/**
66132
* RoomScope requires a LocalContext, which would normally be
67133
* provided in a ComposeView.

0 commit comments

Comments
 (0)