fix(android): Wear Talk works after exiting and reopening (#112383)

* fix(android): scope Wear Talk client to ViewModel

* test(android): exercise Wear ViewModel factory lifecycle

Co-authored-by: NianJiuZst <180004567+NianJiuZst@users.noreply.github.com>

---------

Co-authored-by: NianJiuZst <180004567+NianJiuZst@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
NianJiu
2026-07-28 11:35:10 +08:00
committed by GitHub
parent 7ebd486ed1
commit aa26ecfac1
3 changed files with 58 additions and 5 deletions

View File

@@ -31,10 +31,6 @@ class WearApplication : Application() {
WearGatewayRepository(proxyClient)
}
internal val realtimeTalkClient: WearRealtimeTalkClient by lazy {
WearRealtimeTalkClient(this, gatewayRepository)
}
private val visibleActivities = VisibleActivityTracker()
internal fun onActivityStarted() = visibleActivities.onStarted()

View File

@@ -176,7 +176,7 @@ internal class WearViewModel(
) : AndroidViewModel(application) {
private val app = application as WearApplication
private val repository = app.gatewayRepository
private val realtimeTalkClient = app.realtimeTalkClient
private val realtimeTalkClient = WearRealtimeTalkClient(app, repository)
private val mutableState = MutableStateFlow(WearUiState())
private val eventSequenceTracker = WearEventSequenceTracker()
private val eventSourceTracker = WearEventSourceTracker()

View File

@@ -0,0 +1,57 @@
package ai.openclaw.wear
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotSame
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(application = WearApplication::class, sdk = [35])
class WearViewModelLifecycleTest {
@Test
fun recreatedViewModelGetsALiveTalkClientAfterThePreviousOneClears() {
val app = RuntimeEnvironment.getApplication() as WearApplication
val factory = ViewModelProvider.AndroidViewModelFactory.getInstance(app)
val firstOwner = TestViewModelStoreOwner()
val firstViewModel = ViewModelProvider(firstOwner, factory)[WearViewModel::class.java]
val firstClient = firstViewModel.realtimeTalkClientForTest()
firstOwner.viewModelStore.clear()
val reopenedOwner = TestViewModelStoreOwner()
val reopenedViewModel = ViewModelProvider(reopenedOwner, factory)[WearViewModel::class.java]
val reopenedClient = reopenedViewModel.realtimeTalkClientForTest()
try {
assertFalse(firstClient.scopeForTest().coroutineContext[Job]?.isActive == true)
assertNotSame(firstClient, reopenedClient)
assertTrue(reopenedClient.scopeForTest().coroutineContext[Job]?.isActive == true)
} finally {
reopenedOwner.viewModelStore.clear()
}
}
private class TestViewModelStoreOwner : ViewModelStoreOwner {
override val viewModelStore = ViewModelStore()
}
private fun WearViewModel.realtimeTalkClientForTest(): WearRealtimeTalkClient =
javaClass.getDeclaredField("realtimeTalkClient").run {
isAccessible = true
get(this@realtimeTalkClientForTest) as WearRealtimeTalkClient
}
private fun WearRealtimeTalkClient.scopeForTest(): CoroutineScope =
javaClass.getDeclaredField("scope").run {
isAccessible = true
get(this@scopeForTest) as CoroutineScope
}
}