diff --git a/apps/android/wear/src/main/java/ai/openclaw/wear/WearApplication.kt b/apps/android/wear/src/main/java/ai/openclaw/wear/WearApplication.kt index b3e93d4001a8..3ec342536591 100644 --- a/apps/android/wear/src/main/java/ai/openclaw/wear/WearApplication.kt +++ b/apps/android/wear/src/main/java/ai/openclaw/wear/WearApplication.kt @@ -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() diff --git a/apps/android/wear/src/main/java/ai/openclaw/wear/WearViewModel.kt b/apps/android/wear/src/main/java/ai/openclaw/wear/WearViewModel.kt index e2c8ed3b490d..83df69efa057 100644 --- a/apps/android/wear/src/main/java/ai/openclaw/wear/WearViewModel.kt +++ b/apps/android/wear/src/main/java/ai/openclaw/wear/WearViewModel.kt @@ -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() diff --git a/apps/android/wear/src/test/java/ai/openclaw/wear/WearViewModelLifecycleTest.kt b/apps/android/wear/src/test/java/ai/openclaw/wear/WearViewModelLifecycleTest.kt new file mode 100644 index 000000000000..c50ae71424dd --- /dev/null +++ b/apps/android/wear/src/test/java/ai/openclaw/wear/WearViewModelLifecycleTest.kt @@ -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 + } +}