Structure APIs on iOS

Structure APIs may be accessed through the Home APIs for iOS.

To work with the Structure APIs, first import the GoogleHomeSDK package into your app:

import GoogleHomeSDK

Error handling

Some methods in the Home APIs throw a HomeError, so we recommend that you use a do-catch block to catch HomeError on those calls.

When handling HomeError, check its code and message fields to learn what went wrong.

Any unhandled errors will result in your app crashing.

For more information, see Error handling.

Structure API

Home represents the Home graph, and is the entry point to the Structure API. It provides references to structures, rooms, and devices.

Structure represents a structure in your Home graph. It provides access to structure metadata such as id and name.

Use structures() to get all structures in your account. The structures are returned in the form of a Query, which offers a choice of ways of consuming its data:

API Description
stream() Returns a Publisher that emits each object individually as changes take place.
batched() Returns a Publisher that emits the current result as a Set of objects. Each emitted Set represents the current state of the object graph.
list() Returns the current result as a Set of objects.

The structures().list() call may not immediately return a valid set of structures. If your app is reactive and calls stream() to subscribe to all structure changes to drive the UI, a valid list of structures should eventually be returned. There are other situations where an empty structure list could be returned, for example if the user's phone loses connectivity or if the user has revoked permissions to your app. You should be sure to handle these cases in your app.

@Published public private(set) var structures: [Structure] = []
private var structuresCancellable: AnyCancellable?

  self.structuresCancellable = home
    .structures()
    .batched()
    .receive(on: DispatchQueue.main)
    .map { Array($0) }
    .catch {
      // Failed to load structures
      return Just([Structure]())
    }
    .assign(to: \.structures, on: self)

Sample Structure calls

Get a set of structures

Calling list() on a Query<Structure> returns the most recent Set of elements:

// Get a stream of all structures accessible to the user
let allStructuresChanges = self.home.structures()
let allStructures = try? await allStructuresChanges.list()

When designing a reactive app, you'll want to use batched() and stream() calls rather than list(), because these automatically produce data when the home graph changes.

Get structure properties

With the list of structures in hand, you can access the properties for them:

// Get a stream of changes taking place on a structure.
let structureChanges = try await home.structures().list().filter { $0.id == structureID }

// Get a snapshot of the structure.
let structure = structureChanges.first!

// Get structure properties
print("id \(structure.id) ")
print("name \(structure.name) ")

Find a structure by name

If you know the name of a structure, you can also access it using the name property:

do {
  structure1 = try await home.structures().list().first(where: { $0.name == "Main House" })
} catch let _ as HomeError {
  // Code for handling the exception
}

From there, properties, rooms, and devices for each structure are accessible.

Work with multiple structures

To use more than one structure, get a separate reference to each structure:

var structure1: Structure!
var structure2: Structure!
do {
  structure1 = try await home.structures().list().first(where: { $0.name == "Main House" })
} catch let _ as HomeError {
  // Code for handling the exception
}
do {
  structure2 = try await home.structures().list().first(where: { $0.name == "Guest Cottage" })
} catch let _ as HomeError {
  // Code for handling the exception
}

Rooms

A room contains a group of devices. A room is always part of a structure and a structure may have multiple rooms. Removing a room from a structure does not remove the devices in that room from the structure. However, if the room is deleted, the devices in that room become unassigned.

Use Home.rooms() to retrieve all the rooms in the account, then use roomID = device.roomID to display the corresponding devices in each room.

self.home.rooms().batched()
  .combineLatest(self.home.devices().batched())
  .receive(on: DispatchQueue.main)
  .catch { error in
    // Failed to load rooms and devices
    return Just((Set<Room>(), Set<HomeDevice>()))
  }
  .map { rooms, devices in
    var devicesByRoom = [Room: [HomeDevice]]()
    for room in rooms where room.structureID == currentStructureID {
      devicesByRoom[room] = devices.filter { $0.roomID == room.id }
    }
    return devicesByRoom
  }.assign(to: &self.$devicesByRoom)

Sample Room calls

Get a list of rooms

Using the Home class, you can get a list of rooms and access the properties for them:

let allRoomsChanges = self.home.rooms()
let allRooms = try await allRoomsChanges.list()
let room = allRooms.first!
XCTAssertTrue(allRooms.contains(room))

print("id \(room.id) ")
print("name \(room.name) ")

Create a room

To create a new room in a Structure:

let testName = "Test Room Name"
var newRoom: Room!
do {
  newRoom = try await structure.createRoom(name: testName)
  XCTAssertNotNil(newRoom)
} catch let _ as HomeError {
  // Code for handling the exception
}

Delete a room

Or, alternatively, you can delete a room:

val roomToDelete = structure.rooms().list().filter { it.name == "room_id1" }.firstOrNull()
    structure.deleteRoom(roomToDelete!!)

You can also delete a room using its ID:

let roomToDelete = allRooms.first(where: { $0.id == room.id })
if let roomToDelete1 = roomToDelete {
  do {
    try await structure.deleteRoom(roomToDelete1)
  } catch let _ as HomeError {
    // Code for handling the exception
  }
}

If a room with devices is deleted, the devices will still be in the structure but no longer assigned to a room.

Move devices to a different room

Structure also lets you move a device to a different room:

do {
  try await structure.move(device: light, to: room)
} catch let _ as HomeError {
  // Code for handling the exception
}

Change the name of a room

Call the setName(_:) method to change the name of a room:

let updatedRoom = try await theRoom.setName("new room name")

When changing the name of a room, the original Room struct stays the same and the change is reflected in the returned updated Room object.

Names will be truncated if over the 60 Unicode code point (character) limit and no errors will be thrown. Developers are responsible for handling long names and, for example, can decide if they want to inform users names will be truncated.

Device groups

A device group represents a user-defined collection of devices within a single structure.

Like physical rooms, device groups belong to a single structure. But unlike rooms, device groups are logical groupings. A device can belong to multiple device groups simultaneously. Device groups can contain up to 100 devices.

Device groups are represented by the Group entity, which is distinct from HomeDevice.

Get a list of device groups

To observe all device groups in a structure:

for await groups in structure.deviceGroups.batched() {
  for group in groups {
    print("Found group: \(group.name) with ID: \(group.id)")
  }
}

Create a device group

Use the GroupManagementTrait on the structure to create a device group:

let groupsManagementTrait = try await structure.traits(Google.GroupManagementTrait)
let _ = try await groupsManagementTrait.createUserDefinedGroup(withName: "Kitchen Lights",
         withDevices: [light1, light2])

Manage group membership

To add members to an existing group:

let groupManagementTrait = try await structure.traits(Google.GroupManagementTrait.self)
groupManagementTrait.addGroupMembers(
  groupObjectId: group.id,
  memberDeviceObjectIds = [light3.id],
)

To remove members from a group:

let groupManagementTrait = try await structure.traits(Google.GroupManagementTrait.self)
_ = try await groupManagementTrait.removeGroupMembers(
            groupObjectId: group.id,
            memberDeviceObjectIdsArray: Array(removedIDs)

Change the name of a device group

To rename a device group, update the GroupTrait's name field:

let groupTrait = group.traits.get(Google.GroupTrait.self)
_ = try await groupTrait.update { $0.name = "Movie Corner" }

Delete a device group

To delete a device group from a structure:

let groupManagementTrait = try await structure.traits.get(Google.GroupManagementTrait.self)

_ = try await groupManagementTrait.deleteGroup(groupObjectId: group.id)