DreamService in Android: Integrating Jetpack Compose for Dream Screens
DreamService, introduced in Android 4.2, allows displaying custom UI on the device while idle during charging. The component inherits from Service and supports interactive elements, including buttons. Activation requires manual user configuration in display settings. This solution is suitable for creating dynamic screens without affecting the main app lifecycle.
Declaring the service in AndroidManifest.xml is standard:
<service
android:name=".MyDreamService"
android:exported="true"
android:description="@string/dream_service_description"
android:icon="@drawable/dream_service_icon"
android:label="@string/dream_service_label"
android:permission="android.permission.BIND_DREAM_SERVICE">
<intent-filter>
<action android:name="android.service.dreams.DreamService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
The basic implementation uses onAttachedToWindow() to configure the view:
class MyDreamService : DreamService() {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
isInteractive = false
isFullscreen = true
setContentView(R.layout.dream_service_layout)
}
}
Integrating Jetpack Compose into DreamService
The setContentView method works only with Views, so a ComposeView is created for Compose. However, DreamService does not provide ViewTreeLifecycleOwner and SavedStateRegistryOwner, which are critical for Compose to function correctly.
The solution is to extend DreamService with an implementation of SavedStateRegistryOwner:
class MyDreamService : DreamService(), SavedStateRegistryOwner {
private val lifecycleRegistry = LifecycleRegistry(this)
private val savedStateRegistryController = SavedStateRegistryController.create(this)
override val lifecycle: Lifecycle
get() = lifecycleRegistry
override val savedStateRegistry: SavedStateRegistry
get() = savedStateRegistryController.savedStateRegistry
}
In onAttachedToWindow(), synchronize the lifecycle and registry:
val composeView = ComposeView(this).apply {
setViewTreeLifecycleOwner(this@MyDreamService)
setViewTreeSavedStateRegistryOwner(this@MyDreamService)
setContent {
DreamServiceTheme {
DreamScreen(
onOpenSettings = {
openSettingsAndExitDream()
}
)
}
}
}
setContentView(composeView)
Don't forget to call lifecycle methods in DreamService callbacks (onAttachedToWindow, onDetachedFromWindow, etc.) for proper state synchronization.
Key Limitations and Best Practices
- Activation: Users must manually enable Daydream in settings. Automatic launch is impossible.
- Lifecycle: Ensure full implementation of
LifecycleOwnerfor Compose, otherwise the UI will not update. - Performance: Avoid heavy animations — the device is in sleep mode.
- Interactivity:
isInteractive = trueallows buttons, but requires caution regarding power consumption.
List of implementation steps:
- Declare the service in the manifest with
BIND_DREAM_SERVICEpermission. - Inherit from
DreamServiceand addSavedStateRegistryOwner. - Initialize
LifecycleRegistryandSavedStateRegistryController. - In
ComposeView, set owners beforesetContent. - Test on a real device in charging mode.
Key Takeaways
- DreamService is a full-fledged UI hoster besides Activity, supporting Compose after lifecycle adjustments.
- Manual activation in display settings is mandatory.
- Resolve
ViewTreeLifecycleOwnerissues by extending the service. - Suitable for idle screens: clocks, notifications, mini-games.
- Power saving is critical — minimize draw calls.
This feature expands Android development capabilities, allowing customization of device behavior in non-standard scenarios. For senior developers, it is useful in the context of deep understanding of platform services and their integration with modern UI frameworks.
— Editorial Team
No comments yet.