Build an Android App for Matter

Stay organized with collections Save and categorize content based on your preferences.

1. Welcome

Built with the goal of unifying IoT standards, Matter connects smart home devices across various ecosystems like Google Home, Zigbee, Bluetooth Mesh, Z-Wave, and more.

Mobile devices are a central interaction point with smart home devices. If you'd like to build your own Android apps to support Matter devices, we can help you get started fast.

The Google Home Sample App for Matter (GHSA for Matter) showcases the Home Mobile SDK APIs, allowing users to commission and share devices. You can also use the sample app as a learning tool to better understand key Matter concepts, as well as a tool to debug and troubleshoot interactions with Matter devices.

What you'll do

In this Codelab, you'll download the source code for the sample app and learn how to use the Home Mobile SDK to commission and share devices. You'll also learn how to use commissioning and Cluster libraries from the Matter repo (connectedhomeip).

After you download the sample app, we'll review the source code in Android Studio and implement the following Home Mobile SDK APIs:

You'll also learn more about commissioning concepts, Matter fabrics, and how to control Matter devices.

What you'll need

Before you begin, make sure to complete the following steps:

You don't need a hub, for example a Google Nest Hub (2nd Generation), to commission and control devices with the sample app. However, if you want to use the Google Home app to control devices that you've commissioned with the sample app, you'll need a hub.

2. Get set up

The Sample app GitHub repository includes third party libraries from the Matter repo (connectedhomeip). These native libraries are over 50MB, and require the use of Git Large File Storage (LFS).

The codelab starter app is located in the codelab branch. To start working with the codelab source code, you can download the ZIP file. This ZIP includes the Matter SDK native libraries without the need for Git LFS:

You'll use this codelab ZIP file to build a working sample.

Codelab versions

The codelab branch is tagged with the 1.0.1 release of the sample app. To compare your updates as you work through each step, you can download the completed source code for the 1.0.1 release.

If you'd like to clone the GitHub repository, follow the instructions on the Sample app README.


We'll guide you through the source code required to share and commission devices, but it might help to be aware of the following dependencies before you get started:

  • Home Mobile SDK.
    implementation ''
  • Matter SDK libraries.
    // Native libs
    implementation fileTree(dir: "third_party/connectedhomeip/libs", include: ["*.jar", "*.so"])
  • Material Design. To learn more, refer to MDC-103 Android: Material Theming with Color, Elevation and Type (Kotlin) and Material Theme Builder.
    implementation ''
  • Proto DataStore, used to persist app data. Datastore Repositories and Serializers are stored in java/data, including schemas for devices and user preferences. To learn more about DataStore, refer to Working with Proto DataStore.
    implementation "androidx.datastore:datastore:$dataStoreVersion"
    implementation 'androidx.datastore:datastore-core:1.0.0'
    implementation ''
  • Hilt to persist data and support dependency injection.
    kapt ''
    implementation ''

Source code

The user interface and most of the functionality has already been created for you.

For this codelab, we'll be adding Matter functionality to the following files:

  • java/commissioning/AppCommissioningService: allows you to commission devices to the development fabric
  • java/screens/HomeFragment and java/screens/HomeViewModel.kt: includes the Home Mobile SDK commissioning functionality
  • java/screens/DeviceViewModel: includes the Share Device API calls

Each file is commented with the code-block that you'll be modifying, for example:

// CODELAB: add commissioningFunction()

This allows you to quickly locate the corresponding section in the codelab.

3. Commission to Google

Before you can control devices and allow them to communicate with each other within the same fabric, they need to be commissioned by a Commissioner, which in this case is this sample application, the Google Home Sample App for Matter.

It's important to understand the following concepts about Matter commissioning:

  • Fabrics allow devices to communicate with each other.
  • Fabrics maintain a shared set of unique credentials.
  • Ecosystems are responsible for issuing trusted root certificates, assigning fabric IDs, and assigning unique node IDs. An ecosystem is the back-end service of a commissioner, for example Google Play Services.
  • Devices can be commissioned to more than one fabric.

To commission a device, you'll need to use the CommissioningClient API. A call to .commissionDevice() returns an IntentSender, which launches the proper activity in Google Play Services:

interface CommissioningClient {
  Task<IntentSender> commissionDevice(CommissioningRequest request);

In the next sections, we'll go over the minimal code required to commission devices to the Google fabric.

Step 1: Activity Launcher

To handle the IntentSender from the CommissioningClient, you can use an ActivityResultLauncher:

private lateinit var commissioningLauncher: ActivityResultLauncher<IntentSenderRequest>
commissioningLauncher = registerForActivityResult(
) { result: ActivityResult ->
    if (result.resultCode == RESULT_OK) {
        Timber.d(TAG, "Commissioning succeeded.")
    } else {
        Timber.d(TAG, "Commissioning failed. " + result.resultCode)

Step 2: Commissioning function

Here's a basic example that uses the CommissioningClient API to commission a device to the Google fabric.

  1. The commissioning process starts with the commissionDevice() function. First, a CommissioningRequest is defined. With this default configuration, devices are commissioned only to the Google fabric.
  2. Matter is the entry point for the Home Mobile SDK. In the next call, .getCommissioningClient gets a CommissioningClient by this (Activity).
  3. .commissionDevice() accepts the CommissioningRequest.
  4. And finally, .addOnSuccessListener is called to process the CommissioningResult and launch the Google Play Services (GPS) Commission Device Activity.
private fun commissionDevice() {
    val request: CommissioningRequest = CommissioningRequest.builder().build()
        .addOnSuccessListener { result ->

Once a device is added to the Google fabric, users can control their devices through the Google Home App, Android power controls, voice, and Nest touch screen devices.

Next, you'll learn how to commission a device to a development fabric.

For an overview of the user interface during the commissioning process, refer to the Google Home Sample App for Matter Guide.

4. Commission to a development fabric

Devices can be commissioned to more than one fabric. To manage trusted pairings, devices store a FabricTable containing various FabricInfo members, for example:

  • Fabric identification
  • Node Id assigned by the fabric to the device
  • Vendor Id
  • Fabric Id
  • Device operational credentials

The administrative domain manager (ADM) defines fabric credentials. In the previous scenario, Google Play Services is the ecosystem that acts as a trusted root certificate authority (CA). When you commission devices to the Google fabric, every device includes the same set of fabric credentials, and the same set of CAs.

Custom Commissioning Services

To commission to the Google fabric, we used the default parameters to build the CommissioningRequest in the CommissioningClient API:

val request: CommissioningRequest = CommissioningRequest.builder().build()

If you'd like to control and manage new devices from your app, you need to create a local fabric and obtain the operational credentials to commission devices. In this scenario, your app becomes a unique, independent ecosystem that assigns devices the appropriate node credentials.

You can inform Home Mobile SDK that you'd like to commission devices a local fabric by passing a custom service to the CommissioningRequest:

class CommissioningRequest {
  static CommissioningRequest.Builder builder();

  class Builder {
    Builder setCommissioningService(@Nullable ComponentName commissioningService);

    CommissioningRequest build();

In the next steps, we'll modify the commissionDevice() function to use a custom service. We'll also add an Activity Launcher to the Home fragment and use LiveData objects to manage the API flow.

Step 1: Create a GPS Activity Launcher

First, let's create an Activity Launcher to handle the IntentSender from the CommissioningClient API.

  1. Open HomeFragment in the java/screens/home/ folder.
  2. Replace the // CODELAB: commissionDeviceLauncher declaration comment with the following declaration:
    // The ActivityResult launcher that launches the "commissionDevice" activity in Google Play
    // services.
    private lateinit var commissionDeviceLauncher: ActivityResultLauncher<IntentSenderRequest>
  3. Replace the // CODELAB: commissionDeviceLauncher definition comment with the following code to register and handle the commissioning Activity result:
    commissionDeviceLauncher =
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
          // Commission Device Step 5.
          // The Commission Device activity in GPS has completed.
          val resultCode = result.resultCode
          Timber.d("GOT result for commissioningLauncher: resultCode [${resultCode}]")
          if (resultCode == Activity.RESULT_OK) {
                result, getString(R.string.commission_device_status_success))
          } else {
            viewModel.commissionDeviceFailed(getString(R.string.status_failed_with, resultCode))

Step 2: Create LiveData objects

The success callback of the .commissionDevice() API provides the IntentSender to be used to launch the Commission Device Activity in Google Play Services. In the HomeViewModel, we'll create two LiveData objects to report on the result of this API call:

  • commissionDeviceStatus to track the TaskStatus.
  • commissionDeviceIntentSender to handle the result of the .commissionDevice() call. This LiveData Object will launch the ActivityLauncher that we just created and display the GPS Commission Device Activity to the user.
  1. In private fun setupObservers(), replace the // CODELAB: commissionDeviceStatus comment with the following observer:
    // The current status of the share device action.
    viewModel.commissionDeviceStatus.observe(viewLifecycleOwner) { status ->
      Timber.d("commissionDeviceStatus.observe: status [${status}]")
  2. Next, replace the // CODELAB: commissionDeviceIntentSender comment with the following observer:
    viewModel.commissionDeviceIntentSender.observe(viewLifecycleOwner) { sender ->
      Timber.d("commissionDeviceIntentSender.observe is called with sender [${sender}]")
      if (sender != null) {
        // Commission Device Step 4: Launch the activity described in the IntentSender that
        // was returned in Step 3 where the viewModel calls the GPS API to commission
        // the device.
        Timber.d("*** Calling commissionDeviceLauncher.launch")

Step 3: Call the API

Now that we've written the code to handle the API flow, it's time to call the API, pass in a custom service (that we'll define in the next step), and post to our LiveData objects.

  1. Open HomeViewModel.kt in the java/screens/home/ folder.
  2. Replace the // CODELAB: commissionDevice comment with the following commissionDeviceRequest. setCommissioningService binds AppCommissioningService to a CommissioningService instance, returned in a callback function. When you pass a custom service, Home Mobile SDK will first commission devices to the Google fabric, then send the onboarding payload back to the AppCommissioningService.
    fun commissionDevice(activity: FragmentActivity) {
    val commissionDeviceRequest =
  3. Call .getCommissioningClient(), then call .commissionDevice().

To complete our commissionDevice function, add an addOnSuccessListener and addOnFailureListener and post to the LiveData objects:

      .addOnSuccessListener { result ->
        // Communication with fragment is via livedata
        _commissionDeviceStatus.postValue(TaskStatus.Completed("Received IntentSender."))
      .addOnFailureListener { error ->

After using the sender, consumeCommissionDeviceIntentSender() should be called to avoid receiving the sender again after a configuration change.

/** Consumes the value in [_commissionDeviceIntentSender] and sets it back to null. */
fun consumeCommissionDeviceIntentSender() {

5. Create a CommissioningService

In the commissionDevice() function, we requested to get a CommissioningService from the CommissioningClient API. In this flow, the CommissioningClient API commissions devices to the Google fabric first, then returns a callback that includes the CommissioningRequestMetadata object:

public interface CommissioningService {
interface Callback {
    void onCommissioningRequested(CommissioningRequestMetadata metadata);

Now, we have to inherit the CommissioningService.Callback and provide the functionality required to commission devices to our sample app. Here's an example of a basic CommissioningService implementation:

class MatterCommissioningService : Service(), CommissioningService.Callback {
   private val commissioningServiceDelegate =

   override fun onBind(intent: Intent) = commissioningServiceDelegate.asBinder()

   override fun onCommissioningRequested(metadata: CommissioningRequestMetadata) {
     // perform commissioning


Step 1: Explore the custom AppCommissioningService

To help you get started, we've already defined the basic class structure for our custom CommissioningService. Here's a quick overview of the service functionality. To follow along, open AppCommissioningService in java/commissioning.

We've added the following imports for the Home Mobile SDK APIs:


AppCommissioningService also includes libraries from the Matter repo (connectedhomeip):


Finally, the service includes imports to support Hilt and Kotlin coroutines.

Next, we create the constructor and set a few things up, including the commissioningServiceDelegate, which we'll use to let Google Play Services know when commissioning is complete.

private lateinit var commissioningServiceDelegate: CommissioningService
commissioningServiceDelegate = CommissioningService.Builder(this).setCallback(this).build()

Now it's time to add the commissioning functions.

Step 2: Override onCommissioningRequested

To commission devices to a local fabric, complete the following steps:

  1. Open AppCommissioningService in java/commissioning.
  2. Locate the onCommissioningRequested() function. We've provided a log message that prints out the CommissioningRequestMetadata. Replace the // CODELAB: onCommissioningRequested() comment to start the serviceScope coroutine and get the deviceId.
    // Perform commissioning on custom fabric for the sample app.
    serviceScope.launch {
      val deviceId = devicesRepository.incrementAndReturnLastDeviceId()
  3. Perform commissioning. For this step, we can pass the device information returned in the CommissioningRequestMetadata object. The ChipClient uses this metadata information to create a secure channel between the GHSA for Matter app and your device.
    Timber.d("Commissioning Step 1: ChipClient.establishPaseConnection(): deviceId [${deviceId}]")
    Timber.d("Commissioning Step 2: ChipClient.commissionDevice(): deviceId [${deviceId}]")
    chipClient.awaitCommissionDevice(deviceId, null)
  4. Now that we have the device information, add the device to the devicesRepository. This adds the device to the list of devices on the Home screen.
    Timber.d("Commissioning Step 3: Adding device to repository")
            .setName("REAL-$deviceId") // default name that can be overridden by user in next step
    Timber.d("Commissioning Step 4: Adding device state to repository: isOnline:true isOn:false")
    devicesStateRepository.addDeviceState(deviceId, isOnline = true, isOn = false)
  5. Use the commissioningServiceDelegate to let Google Play Services know that commissioning is complete. In .sendCommissioningComplete(), pass the CommissioningCompleteMetadata.
          "Commissioning Step 5: Calling commissioningServiceDelegate.sendCommissioningComplete()")
          .addOnSuccessListener {
            Timber.d("OnSuccess for commissioningServiceDelegate.sendCommissioningComplete()")
          .addOnFailureListener { ex -> Timber.w(ex, "Failed to send commissioning complete.") }

Run the app

Now that all of the required code is in place to commission to our local fabric, it's time to test it. Choose your Android device, and run the app. From the Home screen, tap Add Device and complete the steps to commission your device.

When commissioning completes, your device now participates in two fabrics: Google Home, and your local fabric. Each fabric has its own set of credentials and a unique, 64-bit fabric ID.

6. Control devices

Commissioning to a development fabric allows you to use the libraries from the Matter repo (connectedhomeip) to control devices from the sample app.

We've created some helper classes to make it easier to access device Clusters and send commands. To learn more, open ClustersHelper in java/clusters. This Singleton helper imports the following libraries to access device information:

import chip.devicecontroller.ChipClusters
import chip.devicecontroller.ChipStructs

We can use this class to get the On/Off Cluster for a device, then call .toggle:

suspend fun toggleDeviceStateOnOffCluster(deviceId: Long, endpoint: Int) {
  val connectedDevicePtr =
      try {
      } catch (e: IllegalStateException) {
        Timber.e("Can't get connectedDevicePointer.")
  return suspendCoroutine { continuation ->
    getOnOffClusterForDevice(connectedDevicePtr, endpoint)
            object : ChipClusters.DefaultClusterCallback {
              override fun onSuccess() {
              override fun onError(ex: Exception) {
                Timber.e("readOnOffAttribute command failure: $ex")

Toggle a device

After you commission a device, the payload returned in the CommissioningResult gets added to the DataStore. This gives our app access to device information that we can use to send commands.

Matter apps are event driven. When the Matter stack is initialized, Cluster services listen for incoming messages. Once a device is commissioned, Matter clients send commands over the secure operational channel that was established during device commissioning.

On the device, packets are validated, decrypted, then dispatched with a callback. Callback functions include the EndpointId, ClusterId, and AttributeId, accessible from the attributePath. For example, this code can be implemented on a Matter device:

void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & attributePath, uint8_t mask, uint8_t type,
                                       uint16_t size, uint8_t * value)
    // handle callback
    ClusterId clusterId     = attributePath.mClusterId;
    AttributeId attributeId = attributePath.mAttributeId;

In the next steps, you'll use the Matter SDK and ClustersHelper to toggle a device.

  1. Go to DeviceViewModel in java/screens/device.
  2. Locate the updateDeviceStateOn function.
  3. Replace the // CODELAB: toggle comment with the code to call clustersHelper, then update the device repository:
    Timber.d("Handling real device")
        try {
          clustersHelper.setOnOffDeviceStateOnOffCluster(deviceUiModel.device.deviceId, isOn, 1)
          devicesStateRepository.updateDeviceState(deviceUiModel.device.deviceId, true, isOn)
        } catch (e: Throwable) {
          Timber.e("Failed setting on/off state")

This function is called from DeviceFragment:

// Change the on/off state of the device
binding.onoffSwitch.setOnClickListener {
  val isOn = binding.onoffSwitch.isChecked
  viewModel.updateDeviceStateOn(selectedDeviceViewModel.selectedDeviceLiveData.value!!, isOn)

Run the app

Run the app to reload your updates. From the Home screen, toggle your device on and off.

7. Share devices with other ecosystems

Sharing a device is referred to as multi-admin flow in the Matter specification.

In the previous steps, we learned that the Home Mobile SDK makes it possible to commission devices to the Google fabric and also to a local fabric. This is an example of multi-admin flow, where devices can be commissioned to more than one fabric.

Now, you may want to share devices with even more fabrics, especially if this is a household where people have their own preferences when it comes to applications and platforms.

The Home Mobile SDK provides this functionality in the ShareDeviceRequest API, allowing you to:

  1. Open a temporary commissioning window for devices.
  2. Change the state of your devices, enabling them to be commissioned to another fabric.
  3. Control your devices from other apps and ecosystems.

In the next steps, you'll use the Home Mobile SDK to share devices.

Step 1: Create a GPS Activity Launcher

Similar to the Commissioning Activity Launcher that we created when we commissioned to a development fabric, we've created a Share Device Activity Launcher to handle the IntentSender from the CommissioningClient API.

  1. Open DeviceFragment in the java/screens/device/ folder.
  2. Replace the // CODELAB: shareDeviceLauncher definition comment with the following code to register and handle the .shareDevice() Activity result:
    shareDeviceLauncher =
    registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
      // Share Device Step 5.
      // The Share Device activity in GPS has completed.
      val resultCode = result.resultCode
      Timber.d("Got result for shareDeviceLauncher: resultCode [[${resultCode}]")
      if (resultCode == RESULT_OK) {
      } else {
        viewModel.shareDeviceFailed(getString(R.string.status_failed_with, resultCode))

Step 2: Review LiveData objects

The success callback of the .shareDevice() API provides the IntentSender to be used to launch the Share Device Activity in Google Play Services. In the DeviceViewModel, we've created two LiveData objects to report on the result of this API call:

  • _shareDeviceStatus to track the TaskStatus.
    // The current status of the share device action.
    viewModel.shareDeviceStatus.observe(viewLifecycleOwner) { status ->
      binding.shareButton.isEnabled = status !is InProgress
      // TODO: binding.actionResultTextView.text = status.toString()
  • _shareDeviceIntentSender to handle the result of the .sharedevice() call.
    viewModel.shareDeviceIntentSender.observe(viewLifecycleOwner) { sender ->
      Timber.d("shareDeviceIntentSender.observe is called with sender [${sender}]")
      if (sender != null) {
        val deviceId = selectedDeviceViewModel.selectedDeviceIdLiveData.value!!
        // Share Device Step 4: Launch the activity described in the IntentSender that
        // was returned in Step 3 where the viewModel calls the GPS API to commission
        // the device.
          OpenCommissioningWindowApi.ChipDeviceController ->
          OpenCommissioningWindowApi.AdministratorCommissioningCluster ->

In the next steps, we'll use these LiveData objects in our .shareDevice() API call.

Step 3: Call the API

Now it's time to initiate a share device task.

  1. Open DeviceViewModel.kt in the java/screens/device/ folder.
  2. Locate the shareDevice() function. Replace the // CODELAB: shareDevice comment with the ShareDeviceRequest. The DeviceDescriptor provides specific information about the device such as its Vendor Id, Product Id, and deviceType. In this example, we hard-code the values.
    val shareDeviceRequest =
            .setDeviceName("Device to share")
  3. Set the CommissioningWindow and parameters. At this point, the temporary commissioning window is open on the device.
  4. Call .getCommissioningClient(), only this time, use the .shareDevice() API. The success callback of the commissioningClient.shareDevice() API provides the IntentSender to be used to launch the Share Device Activity in Google Play Services.
  5. To complete our shareDevice function, add an addOnSuccessListener and addOnFailureListener and post to the LiveData objects:
    .addOnSuccessListener { result ->
          Timber.d("Success on CommissioningClient.shareDevice(): result [${result}]")
          // Communication with fragment is via livedata
          _shareDeviceStatus.postValue(TaskStatus.Completed("Received IntentSender."))
        .addOnFailureListener { error -> _shareDeviceStatus.postValue(TaskStatus.Failed(error)) }

After using the sender, consumeShareDeviceIntentSender should be called to avoid receiving the sender again after a configuration change.

/** Consumes the value in [shareDeviceIntentSender] and sets it back to null. */
private fun consumeShareDeviceIntentSender() {

Run the app

To share your Matter device with other ecosystems, you'll need to have another platform installed on your Android device. We've created another instance of the sample app that you can use as the target commissioner.

Once you have the target commissioner installed on your Android device, verify that you can share your Matter device. The target commissioner app is labeled GHSAFM-TC.

Your devices can now participate in three fabrics:

  1. The Google fabric.
  2. Your local fabric (this app).
  3. This third fabric that you've just shared the device with.

8. Next Steps


Congratulations, you've successfully completed this Codelab and learned how to commission and share devices using the Home Mobile SDK.

If you're having issues with the sample app, try completing the steps to verify your environment:

If you have questions about using the sample app or discover a code bug, you can submit issues to the Issue Tracker in the GitHub repository:

To get official guidance from Google on technical questions, use the Smart Home Developer Forum:

To get technical support from the community, use the google-smart-home tag on Stack Overflow: