Use the Configuration Framework

Last edit: 2023.10.16

TomTom Digital Cockpit supports customization on many levels, from configuration of functional features to theming of the user interface. One form of customization is achieved through the use of the TomTom Digital Cockpit configuration framework which can be used to specify, for example, API keys or configuration feature flags to toggle features on or off and other values that a component in the IVI system can use to configure itself at runtime.

A component such as an IVI service can use the configuration framework to define a configuration. A configuration may be either static or dynamic.

Static configurations are loaded once at startup and have constant values that do not change at runtime. This type of configuration is suitable for specifying, for example: URLs, authentication keys, or certificates.

Dynamic configurations can change at runtime. They start off with initial values (factory defaults) and their values can change while the system is running. For example, they can store user preferences which the user can set to a different value while using the system. Dynamic configurations keep hold of the full history of default values. This allows an existing setting value to be updated from any previous version to the latest suitable value in a compatible way.

Every module may use static configurations directly via a static configuration provider, or access settings via settings services. Dynamic configurations can only be used by settings services.

This document describes how to create static and dynamic configurations using the configuration generator and how to use these configurations in the application.

Overview of the example application

The example application adds the account frontend to the default TomTom Digital Cockpit application. The account frontend adds new panels to show account information or a login page, which can be invoked by a menu item. The account status is managed by the accounts service. When the user is authenticated, the user name is stored in persistent storage by the accounts settings service, so the user does not need to log in again after a restart of the application. The source code for the frontend and service can be found in the examples/plugin directory. See also Create an IVI service and the @IviService annotation.

To demonstrate usage of different configurations, the accounts service will use a static configuration to get the URL of the online API endpoint to authenticate the user. The login will be stored for a limited time. The accounts settings service will provide the time, as specified by a dynamic configuration. For that, you need to perform the following steps:

Create configuration files

Static and dynamic configurations are defined in JSON files located in the configurations/static and configurations/dynamic directories of the source set. For example: <moduleRoot>/src/main/configurations/static.

The JSON file schema is fully documented in the API reference documentation of IviConfigurationGeneratorConfig.

Static configuration file

To create a static configuration for the accounts service, create a JSON file in the configurations/static directory:

examples/plugin/service/src/main/configurations/static/com.example.ivi.example.plugin.service.json

1{
2 "packageName": "com.example.ivi.example.plugin.service",
3 "keys": [
4 {
5 "name": "onlineAccountEndpointConfigKey",
6 "description": "The URI of the endpoint of the online account API",
7 "type": "String",
8 "value": "https://www.example.com/account/"
9 }
10 ]
11}

This file defines a string configuration item with a key "onlineAccountEndpointConfigKey" that belongs to the package com.example.ivi.example.plugin.service. The configuration contains the URL of the online API endpoint, as specified by the "value" field.

Note: The "name" field must have a ConfigKey suffix, and be suitable for use as a Kotlin property name.

Note: The "description" field is optional. It is used as KDoc for the generated configuration keys.

Dynamic configuration file

To create a dynamic configuration for the accounts settings service, create a JSON file in the configurations/dynamic directory:

examples/plugin/settingsservice/src/main/configurations/dynamic/com.example.ivi.example.plugin.service.json

1{
2 "version": 2,
3 "packageName": "com.example.ivi.example.plugin.service",
4 "keys": [
5 {
6 "name": "onlineLoginValidPeriodInDaysConfigKey",
7 "description": "Number of days that an online login stays valid.",
8 "type": "Long",
9 "values": [
10 {
11 "value": 1,
12 "fromVersion": 1,
13 "updateStrategy": "always"
14 },
15 {
16 "value": 30,
17 "fromVersion": 2,
18 "updateStrategy": "always"
19 }
20 ]
21 }
22 ]
23}

This file defines a long number configuration with an "onlineLoginValidPeriodInDaysConfigKey" key that belongs to the package com.example.ivi.example.plugin.service.

Note: The "name" field must have a ConfigKey suffix, and be suitable for use as a Kotlin property name.

Note: The "description" field is optional. It is used as KDoc for the generated configuration keys.

The "version" field specifies the revision number of the configuration file. It must be increased with any change to the configuration. The configuration keeps values for previous versions. These are used for updating old values in the device's persistent storage to the current version.
The configuration version is 2, so the current value is 30. The "updateStrategy" field defines how old values are updated. In the example, it defines that old values are overridden with the new values. See the API reference documentation for ConfigurationUpdateStrategy. The latest configuration version will be stored on the device, and will remain unchanged until a configuration file with a newer version is loaded.

Enable the configuration generator

The configuration JSON files are suitable for human editing but not yet ready to be used in the application. To make the configurations available in the application, they need to be processed by the configuration generator.

The configuration generator looks for JSON files and processes them into:

  • Kotlin source files with configuration keys that can be used by the application. Generated keys are wrapped into StaticConfigurationKey or DynamicConfigurationKey objects.
  • Resource files with static configuration values.
  • Android asset files with dynamic configuration values.

To enable the configuration generator, add following Gradle configuration into Gradle build configurations of modules that contains the configuration. For the given example, these are the accounts service module examples/plugin/service/build.gradle.kts and the accounts settings service module examples/plugin/settingsservice/build.gradle.kts:

1ivi {
2 configurationGenerator {
3 enabled = true
4 }
5 // The configuration framework is an experimental feature, and has to be explicitly opted in.
6 optInToExperimentalApis = true
7}

Use the menu item of Android Studio to run the configuration generator for the current project: navigate to Build > Run Generate Sources Gradle Tasks.

Note: The configuration generation tasks are variant specific and also are a dependency of the respective variant specific AGP task, so they are executed automatically during assemble<BuildVariant> or build tasks. For example, you can run these Gradle tasks to get the configuration generator to process the input files:

1# Either generate configurations for all projects.
2./gradlew generateDebugConfigurations
3
4# Or generate configurations and assemble `Debug` variant for all projects.
5./gradlew assembleDebug
6
7# Or generate configurations for services projects only.
8./gradlew examples_plugin_settingsservice:generateDebugConfigurations examples_plugin_service:generateDebugConfigurations
9
10# Or generate configurations and assemble `Debug` variant for services projects only.
11./gradlew examples_plugin_settingsservice:assembleDebug examples_plugin_service:assembleDebug

Access the configurations via configuration providers

Static configuration value

Static configurations are provided by the static configuration provider that is in the context of IVI service IviServiceHostContext.staticConfigurationProvider.

To get the configuration value of the StockAccountsService, use the StaticConfiguration.onlineAccountEndpointConfigKey variable.

src/main/kotlin/com/example/ivi/example/plugin/service/StockAccountsService.kt

1import com.example.ivi.example.plugin.service.StaticConfiguration.onlineAccountEndpointConfigKey
2
3class StockAccountsService(iviServiceHostContext: IviServiceHostContext) :
4 AccountsServiceBase(iviServiceHostContext) {
5
6 private val onlineAccountEndpoint =
7 iviServiceHostContext.staticConfigurationProvider[onlineAccountEndpointConfigKey]
8}

The onlineAccountEndpoint is a string with the configuration value that is used during user log in. The logIn method calls logInOnline() to authenticate the user. The latter does not make a real network query but only does simple validation.

src/main/kotlin/com/example/ivi/example/plugin/service/StockAccountsService.kt

1private fun logInOnline(username: String, password: SensitiveString): Account? =
2 takeIf { isValidUsername(username) && isValidPassword(password.value) }?.run {
3 println("Pretend making an online request to '$onlineAccountEndpoint'.")
4 Account(username)
5 }

Dynamic configuration value

Dynamic configurations are used by settings services to initialize settings.

First, add a new setting onlineLoginValidPeriodInDays to the accounts settings service that holds the number of days the user may stay logged in.

src/main/kotlin/com/example/ivi/example/plugin/settingsserviceapi/AccountSettingsService.kt

1interface AccountSettingsService {
2 @IviSetting(accessMode = IviSettingAccessMode.READ_ONLY)
3 val onlineLoginValidPeriodInDays: Long
4}

By default, the settings service initializes properties with values from dynamic configurations. The service implementation only needs to set the configuration key and the setting key. The latter can be easily made from the former.

src/main/kotlin/com/example/ivi/example/plugin/settingsservice/StockAccountSettingsService.kt

1// Both variables are generated by the configuration generator.
2import com.example.ivi.example.plugin.service.DynamicConfiguration.settingKeyPrefix
3import com.example.ivi.example.plugin.service.DynamicConfiguration.onlineLoginValidPeriodInDaysConfigKey
4
5class StockAccountSettingsService(iviServiceHostContext: IviServiceHostContext) :
6 AccountSettingsServiceBase(iviServiceHostContext) {
7
8 // The `onlineLoginValidPeriodInDays` setting has default values provided by the dynamic
9 // configuration key.
10 // All methods for this setting have default implementation.
11 override val onlineLoginValidPeriodInDaysConfigurationKey: LongDynamicConfigurationKey =
12 onlineLoginValidPeriodInDaysConfigKey
13
14 // This key is used to load and store the setting value in the persistent storage on the device.
15 override val onlineLoginValidPeriodInDaysSettingKey: LongSettingKey =
16 onlineLoginValidPeriodInDaysConfigurationKey.toSettingKey(
17 // The setting is application-wide, regardless of the currently selected user profile.
18 SettingScope.APPLICATION,
19 settingKeyPrefix
20 )
21}

Then the accounts service can use the onlineLoginValidPeriodInDays settings to validate the logged in account when it is needed.