Building settings based on modules

Problem description

Settings are often modular. They could be based on different level of modularity :

  • depending on if module has been taken for this profile
  • depending on back-end configuration (for example FAQs or channel scan)
  • depending on hardware capability (for example HDMI CEC depends on TV capability)

You should avoid having all settings screen and service in one module for two main reasons :

  • Build size. Most settings are mandatory module but some are not. So you need to avoid to embed code that does not make sense for a platform.
  • Logic. Settings of a module should be part of the module responsible for the feature.

But you also need to mutualize screens and behaviors because there is a similar root to all screens.

For example, this is the first level of settings : “First level”

And this is a specific screen that is a classic multi choice settings : “First level”

Proposed solution

SettingsTreeService+ getChildren(id): Array<SettingsEntry>+ getParent(id): SettingsEntry+ getEntry(id): SettingsEntrySettingsEntry+ id: String+ parentId: String+ title: String+ isFolder: Boolean+ route: String+ previewType: String+ previewValue: ObjectAbstractSettingsScreen+ prepareData()SettingsScreenMSystemInfoSetting+ id: String = SETTINGS.SYSTEM_INFO+ title: String = "System information"+ type: String = SETTING+ hasChildren: Boolean+ route: String+ previewType: String+ getPreviewAsync()+ isEnabledAsync()
getChildren:
  • build and create SettingsEntry
  • with id: call getSharedObject or specific implementaton
    getSharedObject(id).map(new SettingsEntryItem()
getChildren:...
settingsModule
settingsModule
Text is not SVG - cannot display
ParentControlModule
ParentControlModule
AbstractSettingsScreen+ prepareData()AgeRatinSettingScreenMParentalControlSetting+ id: String = SETTINGS.PARENTAL_CONTROL+ title: String = "Parental control"+ type: String = SETTING+ parentId: String = SETTINGS.PARENTAL_CONTROL+ hasChildren: Boolean = true+ route: String = "ageRatingSetting"+ previewType: String = "none"+ getPreviewAsync(): return Promise.resolve(null)+ isEnabledAsync(): return Promise.resolve(true)MAgeRatingItemSetting+ id: String = SETTINGS.PARENTAL_CONTROL.AGE_RATING+ type: String = SETTINGS.PARENTAL_CONTROL+ getPreviewAsync(): return Promise.resolve(18)MChannelLockSetting+ id: String = SETTINGS.PARENTAL_CONTROL.CHANNEL_LOCK+ type: String = SETTINGS.PARENTAL_CONTROL+ getPreviewAsync(): return Promise.resolve(null)
Text is not SVG - cannot display
AccessibilityModule
AccessibilityModule
AbstractSettingsScreen+ prepareData()SystemLanguageSettingScreenMAccessibilitySetting+ id: String = SETTINGS.ACCESSIBILITY+ title: String = "Accessibility"+ type: String = SETTING+ parentId: String = SETTINGS.PARENTAL_CONTROL+ hasChildren: Boolean = true+ previewType: String = "none"+ getPreviewAsync(): return Promise.resolve(null)+ isEnabledAsync(): return accessibilityService.isEnabled()MSystemLanguageItemSetting+ id: String = SETTINGS.ACCESSIBLITY.SYSTEM_LANG+ type: String = SETTINGS.ACCESSIBILITY+ getPreviewAsync(): return Promise.resolve("en")MAudioLanguageItemSetting+ id: String = SETTINGS.PARENTAL_CONTROL.AUDIO_LANG+ type: String = SETTINGS.ACCESSIBILITY+ getPreviewAsync(): return Promise.resolve("fr")
Text is not SVG - cannot display

To define settings structure, use SharedObject in modules.

SharedObject structure and logic

Each module can define SharedObject of type SETTINGS. This will be considered as first level of the settings. Then id of this “first level” could be used as type of other SharedObject to define “subLevel”. For example, based on above schema we have

  • SETTINGS
    • SETTINGS.PARENTAL_CONTROL
    • SETTINGS.PARENTAL_CONTROL.AGE_RATING
    • SETTINGS.PARENTAL_CONTROL.CHANNEL_LOCK
  • SETTINGS.ACCESSIBILITY
    • SETTINGS.ACCESSIBILITY.SYSTEM_LANGUAGE
    • SETTINGS.ACCESSIBILITY.AUDIO_LANGUAGE
  • SETTINGS.SYSTEM_INFO

For the need of the example, use 2 methods in it :

  • getPreviewAsync : will allow to retrieve preview value for an entry if it exists. It can call service of the module if required
  • isEnabledAsync : will allow to check if the settings is enabled (i.e. should be visible). For example, CEC will depend on hardware capability, FAQ on BE results…

SettingsTreeService

Create a SettingsTreeService in SettingsModule. Its role is to build settings architecture and allow screens to retrieve those information. It will expose 3 methods :

  • getEntry : should allow to retrieve information relative to an Entry based on its id
  • getChildren : should allow to retrieve children of an Entry based on its id
    • call getSharedObject and map SharedObject to a SettingsEntry (be carefull SharedObject are mixins, so you should call .create() on them)
    • should only build a SettingsEntry if call to isEnabledAsync returns true
    • should call getPreviewAsync and set result in previewValue (do not put methods in a Model)
    • can do specific stuff based on id (for example FAQ will be dynamically created and can’t be based on SharedObject)
  • getParent : should allow to retrieve parent of en Entry based on its id

Screens

To mutualize screen behaviour it is important that you create multiple AbstractSettingsScren. Then you will be able to inherit from those AbstractSettingsScreen to build SettingsScreen of your module. Do not declare all SettingsScreen in SettingsModule. It is very important to use modularization for screens.

As soon as you arrive at a specific screen, use routing to move to another settings. It will allow you to only keep action and logic specific to the selected settings in one screen. Otherwise, it will become harder and harder to maintain.

Do not forget to add settings module as dependencies in your package.json of your module (modules/myModule/package.json) :

"wtvDependencies": {
    "settings": "1.0.0"
}

Conclusion

  • Mutualise setting screens and views in SettingsModule and then define specific settings screens in modules that inherits or uses element of Settings module.
  • Define settings screens in each module and use routing
  • Handle settings choices inside module responsible for the feature instead of inside the module responsible for the settings
  • Use SharedObject to avoid full module loading for settings tree building