Pre-Requisites

Understanding preference structure

This is how a typical preference page will look like:

Preference Page contains 2 sections:

  1. Category-level preference settings (Sections)

  2. Overall Channel-level preference

Preferences data structure

struct PreferenceData: Codable {
  var sections: [Section]?
  var channelPreferences: [ChannelPreference]?

  enum CodingKeys: String, CodingKey {
    case sections
    case channelPreferences = "channel_preferences"
  }

}

struct ChannelPreference: Codable {
  var channel: String
  var isRestricted: Bool

  enum CodingKeys: String, CodingKey {
    case channel
    case isRestricted = "is_restricted"
  }

}

struct Section: Codable {
  var name: String?
  var description: String?
  var subcategories: [Category]?
}

struct Category: Codable {
  var name: String
  var category: String
  var description: String?
  var preference: PreferenceOptions
  var isEditable: Bool
  var channels: [CategoryChannel]?

  enum CodingKeys: String, CodingKey {
    case name
    case category
    case description
    case preference
    case isEditable = "is_editable"
    case channels
  }

}

struct CategoryChannel: Codable {
  var channel: String
  var preference: PreferenceOptions
  var isEditable: Bool

  enum CodingKeys: String, CodingKey {
    case channel
    case preference
    case isEditable = "is_editable"
  }

}

enum PreferenceOptions: String, Codable {
  case optIn = "opt_in"
  case optOut = "opt_out"
}

enum ChannelLevelPreferenceOptions: String, Codable {
  case all = "all"
  case required = "required"
}

1.1 Sections

This contains the name, description, and subcategories. We have to loop through the sections list and for every section item if there is a name and description present, then show the heading, and if a subcategories list is present, loop through that subcategories list and show all subcategories under that section heading.

Subcategories can exist without sections as the section is an optional field. In that case, the section’s name will not be available. For sections where the name is not present, you can directly show its subcategories list without showing Heading for the section in UI.

struct Section: Codable {
 var name: String?
 var description: String?
 var subcategories: [Category]?
}

PropertyDescription
namename of the section
descriptiondescription of the section
subcategoriesdata of all sub-categories to be shown inside the section

1.2 Categories (sections -> sub-categories)

This is the place where the user sets his category-level preferences. While looping through the subcategories list for every subcategory item, show the name and description in UI.

struct Category: Codable {
 var name: String
 var category: String
 var description: String?
 var preference: PreferenceOptions
 var isEditable: Bool
 var channels: [CategoryChannel]?

  enum CodingKeys: String, CodingKey {
    case name
    case category
    case description
    case preference
    case isEditable = "is_editable"
    case channels
  }
}
PropertyDescription
categoryThis key is the id of the category which is used while updating the preference.
namename of the category to be shown on the UI
descriptiondescription of the category to be shown on the UI
preferenceThis key indicates if the category’s preference switch is on or off. Get OPT_IN when the switch is on and OPT_OUT when the switch is off
is_editableIndicates if the preference switch button is disabled or not. If its value is false then the preference setting for that category can’t be edited
channelsdata of all category channels to be shown below the sub-category. Loop through it to show checkboxes under every subcategory item.

1.3 Category channels (sections -> sub-categories -> channels)

This contains a list of channels, channel preference status and whether it’s editable or not. While looping through the subcategory list for every subcategory item we have to loop through its channels list and for every channel to show channel level checkbox.

struct CategoryChannel: Codable {
 var channel: String
 var preference: PreferenceOptions
 var isEditable: Bool

  enum CodingKeys: String, CodingKey {
    case channel
    case preference
    case isEditable = "is_editable"
  }

}

PropertyDescription
channelname of the channel to be shown on UI. The same key will be used as id of the channel while updating the preference.
preferenceThis key indicates if the channel’s preference switch is on or off. Get OPT_IN when the switch is on and OPT_OUT when the switch is off
is_editableIndicates if the preference checkbox is disabled or not. If its value is false then the preference setting for that channel can’t be edited

2. Overall channel preferences

It’s a list of all channel-level preferences. We have to loop through the list and for each item, show the UI as given in the below image.

struct ChannelPreference: Codable {
  var channel: String
  var isRestricted: Bool

  enum CodingKeys: String, CodingKey {
    case channel
    case isRestricted = "is_restricted"
  }

}
PropertyDescription
channelname of the channel to be shown on UI. The same key will be used as id of the channel while updating the preference.
is_restrictedThis key indicates the restriction level of channel. If restricted, notification will only be sent in the category where this channel is added as mandatory in notification category settings. True means Required radio button is selected. False means All radio button is selected.

Integration

Get preferences data

Use this method to get preferences data and create the preferences UI by following the above sections. This method should be called first before any update preference methods.

await SuprSend.shared.preferences.getPreferences(args: Preferences.Args(tenantId: "")) 

Returns: async -> PreferenceAPIResponse

Update channel preference in category

Calling this method will opt-in/opt-out users from that category-level channel. When the category’s channel checkbox is editable and the user clicks on the checkbox you can call this method.

await SuprSend.shared.preferences.updateChannelPreferenceInCategory(
  channel: "channel",
  preference: PreferenceOptions,
  category: "category"
)

enum PreferenceOptions: String, Codable {
   case optIn = "opt_in"
   case optOut = "opt_out"
}

Returns: async -> PreferenceAPIResponse

Update category preference

This is category level preference changing method. Calling this method will opt-in/opt-out user from that category. When the category is editable and the switch is toggled you can call this method.

await SuprSend.shared.preferences.updateCategoryPreference(category: "category_value", preference: PreferenceOptions)

enum PreferenceOptions: String, Codable {
  case optIn = "opt_in"
  case optOut = "opt_out"
}

Returns: async -> PreferenceAPIResponse

Update overall channel preference

This method updated the channel-level preference of the user.

await SuprSend.shared.preferences.updateOverallChannelPreference(
  channel: "channel",
  preference: ChannelLevelPreferenceOptions
)

enum ChannelLevelPreferenceOptions: String, Codable {
 case all = "all"
 case required = "required"
}

Returns: async -> PreferenceAPIResponse

Event listeners

All preferences update api’s are optimistic updates. Actual API call will happen in background with 1 second debounce. Since its a background task SDK provides event listeners to get updated preference data based on API call status. Listen to this event listeners and update the UI accordingly.

SuprSend.shared.emitter.on(.preferencesUpdated) { data in
  // update local store so that UI is updated with latest data
}

SuprSend.shared.emitter.on(.preferencesError) { error in
  // show error toast to user
}

Example

Preferences UI example code: PreferencesView.swift and PreferenceModel.swift