Breaking Changes

This guide gives hints and guidance in the case that you need to make a change to your app that might break functionality for current users.

Situations that might result in breaking existing functionality for users:

  • Removing or adding driver capabilities

  • Changing or removing Flow cards

  • Changing a driver's device class

Publishing breaking changes is in general not allowed. Homey users expect that their devices and Flows will continue to work after receiving app updates. As a developer you need to make sure that users never have to experience breaking changes. Almost all users have automatic app updates enabled, this gives you great responsibility as an app developer!

Adding capabilities

You can add new capabilities to your devices simply by adding them to the /drivers/<driver_id>/driver.compose.json. However devices that have already been paired do not automatically receive the new capability. To prevent users of your app from having to re-pair their devices to get the new functionality you can call Device#addCapability() to dynamically add the capability to already-paired devices.

/drivers/<driver_id>/device.js
const Homey = require('homey');

class Device extends Homey.Device {
  async onInit() {
    if (this.hasCapability('windowcoverings_set') === false) {
      // You need to check if migration is needed
      // do not call addCapability on every init!
      await this.addCapability('windowcoverings_set');
    }
  }
}

module.exports = Device;

Removing capabilities

While maintaining your app you may encounter a situation where you want to remove a capability that is no longer useful.

In most cases, it is best to only remove the capability from the driver object in the App Manifest. This makes sure that for already paired devices nothing will break, UI components and Flow cards will remain working. However, the removed capability will not be added to newly paired devices. When applying this migration strategy, it is important that you do not remove the capability listener for the removed capability in the future, this will break functionality for already paired devices.

It is also possible to remove the capability from devices that have already been paired by calling Device#removeCapability(). You should only do this when it is no longer possible to implement the capabilities behaviour.

If the capability you are removing has Flow cards, those cards will also be removed. Flows using these Flow cards will break.

/drivers/<driver_id>/device.js
const Homey = require('homey')

class Device extends Homey.Device {
  async onInit() {
    if (this.hasCapability('windowcoverings_state')) {
      // You need to check if migration is needed
      // do not call removeCapability on every init!
      await this.removeCapability('windowcoverings_state');
    }
  }
}

module.exports = Device;

Deprecating Flow cards

When you want to remove a Flow card from your app, or change how it is constructed (e.g. add or remove arguments) you should add the "deprecated": true flag. This will ensure that users who create new Flows will not have the option to select this Flow card anymore, but users who still have this Flow card as part of their Flows will not experience breaking Flows. When this Flow card is deprecated you can create a new Flow card with the updated functionality that replaces the deprecated one.

Do not remove or change the Flow card run listener in your code, this would still break existing Flows.

/.homeycompose/flow/actions/stop_raining.json
{
  "title": { "en": "Flow Action Title" },
  "deprecated": true
}

Changing the device class

If you need to change the device class of your device, the Device#setClass() method is available to do so.

Some Flow cards might depend on a certain device class, changing the device's device class will result in broken Flows for users in that case.

/drivers/<driver_id>/device.js
const Homey = require('homey');

class Device extends Homey.Device {
  async onInit() {
    if (this.getClass() !== 'light') {
      // You need to check if migration is needed
      // do not call setClass on every init!
      await this.setClass('light').catch(this.error)
    }
  }
}

module.exports = Device;

Deprecating drivers

If all the above does not result in a migration strategy that does not break existing functionality of your driver for current users, it might be best to deprecate the driver as a whole. This can be done by adding the "deprecated": true flag to your driver manifest. Deprecating a driver will ensure that devices that have already been paired will continue to function but the driver cannot be selected to pair new devices with.

/drivers/<driver_id>/driver.compose.json
{
  "name": { "en": "My Driver" },
  "deprecated": true,
  "capabilities": ["onoff", "dim"]
}

Last updated