Android Snippets
repository·main·Indexed 22 days ago
https://github.com/android/snippetsA collection of implementation snippets and sample projects designed to accompany official Android developer documentation. It includes practical examples for Bluetooth Low Energy (BLE) connectivity, UI views, Wear OS Watch Face Push patterns, AppFunction API implementations for AI agents, and Android Car App development including CarAppService, screen navigation, and MapWithContentTemplate.
What's inside android-snippets
- This repository serves as a collection of code snippets used in the official Android developer documentation for Bluetooth Low Energy (BLE) connectivity. It provides practical implementations for the concepts described in the Bluetooth LE guide.
Overview of the Watch Face Push sample project
mainThis project provides code snippets and implementation examples for the 'Watch Face Push' pattern described in the Android developer documentation. It demonstrates how to push data from a handheld device to a Wear OS watch face using Bluetooth Low Energy (BLE).Access Bluetooth LE UI view snippets
mainThis repository serves as a collection of code snippets used in the official Android developer documentation regarding UI views. You can find implementation examples for various UI components by browsing theviews/directory. These snippets are intended to be used as reference implementations for building Android user interfaces.Use Compose in an Activity
mainTo use Jetpack Compose within a standard
ComponentActivity, callsetContentinsideonCreate. This establishes the root of your Compose UI tree.class ExampleActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Greeting(name = "compose") } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }Handle transitive remember dependencies
mainWhen creating a new object that depends on an existing
RememberObserverobject (e.g., aFooobject that is remembered), you should use the dependency as a key in therememberfunction. This ensures that if the original object changes, the dependent object is also re-calculated.While
remember { Bar(foo) }is acceptable, the recommended pattern isremember(foo) { Bar(foo) }to correctly handle updates to the dependency.// Assuming foo is a remembered RememberObserver val foo: Foo = rememberFoo() // Recommended key usage to handle updates to foo: val barWithKey: Bar = remember(foo) { Bar(foo) }Teardown work in onForgotten() and onAbandoned()
mainTo prevent memory leaks and unnecessary resource usage, you must cancel work launched during the object's lifecycle using the appropriate
RememberObservercallbacks:onForgotten(): Use this to cancel work launched fromonRemembered(). This is called when the object is removed from the composition.onAbandoned(): Use this to cancel any work that was launched during the object's construction (theinitblock) if the object was never successfully remembered by the composition.
If you are implementing a
RetainObserver, useonRetiredto cancel work launched fromonRetained.class MyComposeObject : RememberObserver { private val job = Job() private val coroutineScope = CoroutineScope(Dispatchers.Main + job) override fun onForgotten() { // Cancel work launched from onRemembered job.cancel() } override fun onAbandoned() { // Cancel work launched by the constructor if the object wasn't successfully remembered job.cancel() } }Detect desktop engagement mode using WindowInfoTracker
mainTo optimize your UI for desktop environments (e.g., switching between touch-optimized and pointer-optimized layouts), use
WindowInfoTracker.getOrCreate(activity)to collectwindowEngagementInfo. You can check for specific engagement modes, such asWindowEngagementInfo.EngagementMode.PRECISE_POINTER, to determine if the user is interacting via a precise pointing device like a mouse.val windowInfoTracker = WindowInfoTracker.getOrCreate(this@DesktopWindowingActivity) lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { windowInfoTracker.windowEngagementInfo(this@DesktopWindowingActivity) .collect { windowEngagementInfo -> if(windowEngagementInfo.hasEngagementMode(WindowEngagementInfo.EngagementMode.PRECISE_POINTER)){ showDesktopOptimizedUI() } else { showTouchOptimizedUI() } } } }Use Compose in a Fragment
mainYou can integrate Compose into Fragments using several methods:
- Via XML: Use a
ComposeViewinside your fragment's XML layout. It is highly recommended to set aViewCompositionStrategy(e.g.,DisposeOnViewTreeLifecycleDestroyed) to ensure the composition is disposed of correctly when the view's lifecycle is destroyed. - Via View Binding: Access the
ComposeViewthrough your binding object. - Without XML: Programmatically create a
ComposeViewand return it fromonCreateView.
// Example: Using ComposeView in a Fragment with XML class ExampleFragmentXml : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { val view = inflater.inflate(R.layout.fragment_example, container, false) val composeView = view.findViewById<ComposeView>(R.id.compose_view) composeView.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { MaterialTheme { Text("Hello Compose!") } } } return view } } // Example: Using ComposeView without XML class ExampleFragmentNoXml : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { MaterialTheme { Text("Hello Compose!") } } } } }- Via XML: Use a
Implement an AppFunctionService
mainAn
AppFunctionServiceis the entry point for exposing functions to an AI agent. To implement one:- Extend
AppFunctionService. - Annotate the class with
@AppFunctionServiceEntryPoint, providing aserviceNameand theappFunctionXmlFileName. - Use
@AppFunction(isDescribedByKDoc = true)onsuspendfunctions to expose them. The agent uses the KDoc to understand how to call the function. - Use standard exceptions like
AppFunctionInvalidArgumentExceptionorAppFunctionElementNotFoundExceptionto communicate failures to the agent.
Note: This requires
@RequiresApi(36).@RequiresApi(36) @AppFunctionServiceEntryPoint( serviceName = "TaskAppFunctionService", appFunctionXmlFileName = "task_app_function_service", ) abstract class BaseTaskAppFunctionService : AppFunctionService() { @AppFunction(isDescribedByKDoc = true) suspend fun createTask( createTaskParams: CreateTaskParams, ): Task { if (createTaskParams.title == null && createTaskParams.content == null) { throw AppFunctionInvalidArgumentException("Title or content should be non-null") } // ... implementation } }- Extend
Implement CarAppService and manage Sessions
mainTo create a Car App, extend
CarAppServiceand overrideonCreateSession. This method determines whichSessionto provide based on theSessionInfo.displayType. For example, you can return a different session for aDISPLAY_TYPE_CLUSTERthan for the main display. You must also implementcreateHostValidatorto define which hosts are allowed to connect to your service.class MyNavigationCarAppService : CarAppService() { override fun onCreateSession(sessionInfo: SessionInfo): Session { return if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) { ClusterSession() } else { MainDisplaySession() } } override fun createHostValidator(): HostValidator { return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR } }Protect RememberObserver implementations via private wrappers
mainTo prevent consumers from accessing or misusing the
RememberObserverimplementation directly, wrap the observer inside arememberblock and return only the underlying manager or data object. This ensures the lifecycle management (initialization and teardown) remains encapsulated within your library's internal logic.@Composable fun rememberMyManager(): MyManager { // Protect the RememberObserver implementation by never exposing it outside the library return remember { object : RememberObserver { val manager = MyComposeManager() override fun onRemembered() = manager.initialize() override fun onForgotten() = manager.teardown() override fun onAbandoned() { /* Nothing to do if manager hasn't initialized */ } } }.manager }Initialize effect-driven work in onRemembered()
mainWhen implementing
androidx.compose.runtime.RememberObserver, avoid launching cancellable or effect-driven work (like Coroutines) inside the classinitblock or during composition. This can cause work to begin prematurely. Instead, move such work into theonRemembered()callback to ensure it starts when the object is actually successfully remembered by the composition.class MyComposeObject : RememberObserver { private val job = Job() private val coroutineScope = CoroutineScope(Dispatchers.Main + job) // Recommended: Move any cancellable or effect-driven work into the onRemembered() callback. override fun onRemembered() { coroutineScope.launch { loadData() } } private suspend fun loadData() { /* ... */ } }