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 = try await 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 = try await 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 error 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 error as HomeError {
  // Code for handling the exception
}
do {
  structure2 = try await home.structures().list().first(where: { $0.name == "Guest Cottage" })
} catch let error 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 error 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 error 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 error as HomeError {
  // Code for handling the exception
}

API list

Once an instance of Home is created, the following Structure APIs are accessible through it:

API Description
devices() Get all devices visible to this account.
device(id:) Get a Publisher for a specified device that emits the current state and again on any future state updates.
structures() Get all the structures on the Google Account. Returns a Query<Structure> that provides further retrieval and filtering options.
structure(id:) Get the structure with the matching ID.
rooms() Get all the rooms on the Google Account. Returns a Query<strRoom> that provides further retrieval and filtering options.
room(id:) Get a Publisher for a specified room that emits the current state and again on any future state updates.

The Structure has the following APIs:

API Description
deleteRoom(id:) Delete a room with the room ID.
id The unique system ID of the structure.
move(device:, to:) Move a device to a different room in the structure.
move(device:, to:) Move the device with the given ID to the room with given ID.
move(devices:, to:) Moves the given devices to the given room.
move(devices:, to:) Moves the devices with given IDs to the room with given ID.
name The user-provided name of the structure.

Room has the following APIs:

API Description
id The unique system ID of the room.
name The user-provided name of the room.
structureID The unique system ID of the structure to which the room belongs.