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 {
      Logger.error("Failed to load structures: \($0)")
      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
    Logger.error("Failed to load rooms and devices: \(error)")
    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.