Skip to content

Commit

Permalink
Fix: Handle missing AmbientLifecycleObserver in Robolectric (#2474)
Browse files Browse the repository at this point in the history
* Fix: Handle missing AmbientLifecycleObserver in Robolectric

Handles missing AmbientLifecycleObserver in Robolectric tests by catching NoClassDefFoundError and returning null instead of crashing. This allows ambient-aware composables to be tested in Robolectric without requiring a real device or emulator.


---------

Co-authored-by: yschimke <[email protected]>
  • Loading branch information
yschimke and yschimke authored Dec 6, 2024
1 parent f8ccc68 commit 6cd025b
Showing 1 changed file with 60 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ import android.content.Context
import android.content.ContextWrapper
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.wear.ambient.AmbientLifecycleObserver

Expand All @@ -51,49 +54,72 @@ fun AmbientAware(
val activity = LocalContext.current.findActivityOrNull()
val lifecycle = LocalLifecycleOwner.current.lifecycle

var ambientState = remember {
val ambientState = rememberAmbientState(activity, lifecycle)

CompositionLocalProvider(LocalAmbientState provides ambientState.value) {
content(ambientState.value)
}
}

@Composable
private fun rememberAmbientState(
activity: Activity?,
lifecycle: Lifecycle,
): State<AmbientState> {
val ambientState = remember {
mutableStateOf<AmbientState>(AmbientState.Inactive)
}

val observer = remember {
remember {
if (activity != null) {
AmbientLifecycleObserver(
activity,
object : AmbientLifecycleObserver.AmbientLifecycleCallback {
override fun onEnterAmbient(ambientDetails: AmbientLifecycleObserver.AmbientDetails) {
ambientState.value = AmbientState.Ambient(
burnInProtectionRequired = ambientDetails.burnInProtectionRequired,
deviceHasLowBitAmbient = ambientDetails.deviceHasLowBitAmbient,
)
}

override fun onExitAmbient() {
ambientState.value = AmbientState.Interactive
}

override fun onUpdateAmbient() {
val lastAmbientDetails =
(ambientState.value as? AmbientState.Ambient)
ambientState.value = AmbientState.Ambient(
burnInProtectionRequired = lastAmbientDetails?.burnInProtectionRequired == true,
deviceHasLowBitAmbient = lastAmbientDetails?.deviceHasLowBitAmbient == true,
)
}
},
).also { observer ->
ambientState.value =
if (observer.isAmbient) AmbientState.Ambient() else AmbientState.Interactive

lifecycle.addObserver(observer)
}
createObserver(activity, ambientState, lifecycle)
} else {
null
}
}

val value = ambientState.value
CompositionLocalProvider(LocalAmbientState provides value) {
content(value)
return ambientState
}

private fun createObserver(
activity: Activity,
ambientState: MutableState<AmbientState>,
lifecycle: Lifecycle,
): AmbientLifecycleObserver? {
return try {
AmbientLifecycleObserver(
activity,
object : AmbientLifecycleObserver.AmbientLifecycleCallback {
override fun onEnterAmbient(ambientDetails: AmbientLifecycleObserver.AmbientDetails) {
ambientState.value = AmbientState.Ambient(
burnInProtectionRequired = ambientDetails.burnInProtectionRequired,
deviceHasLowBitAmbient = ambientDetails.deviceHasLowBitAmbient,
)
}

override fun onExitAmbient() {
ambientState.value = AmbientState.Interactive
}

override fun onUpdateAmbient() {
val lastAmbientDetails =
(ambientState.value as? AmbientState.Ambient)
ambientState.value = AmbientState.Ambient(
burnInProtectionRequired = lastAmbientDetails?.burnInProtectionRequired == true,
deviceHasLowBitAmbient = lastAmbientDetails?.deviceHasLowBitAmbient == true,
)
}
},
).also { observer ->
ambientState.value =
if (observer.isAmbient) AmbientState.Ambient() else AmbientState.Interactive

lifecycle.addObserver(observer)
}
} catch (e: NoClassDefFoundError) {
// Fails in Robolectric
// java.lang.NoClassDefFoundError: com/google/android/wearable/compat/WearableActivityController$AmbientCallback
null
}
}

Expand Down

0 comments on commit 6cd025b

Please sign in to comment.