How to Use an Application Theme From a Client Application¶
This section describes how to use an application theme from a client application before starting the onboarding or restore flows.
Changes for Android Client¶
The Application Themes module allows you to download custom theming files for an application at runtime. The application is then responsible for providing these files to the Fiori UI Theming component.
The Foundation framework’s ThemeDownloadService.downloadTheme()
API can check for available themes on the server side for the application and download them. The application can automatically apply theming on the controls used from the Fiori library.
This feature requires Android API Level 30 and above as it makes use of the ResourceProvider
feature of the Android operating system. The following sample code provided is relevant for view-based applications built using Kotlin.
In order to apply a theme the application needs to use the Theme.Fiori.Custom
theme to pick up custom colors. This can be changed in the styles.xml
file in the res/values
folder like this:
<style name="AppTheme" parent="Theme.Fiori.Custom"/>
The ThemeDownloadService
then needs to be used within the application to download and apply the custom theme. If the theme should be applied before authentication is performed, the SDKInitializer
must be passed the API Key
copied earlier from the Anonymous Access section of the SAP mobile service cockpit. The resulting code can look like this:
//The variable represents the instance of application.
services.add(ThemeDownloadService(this))
//Add the `apiKey` here to enable anonymous access.
SDKInitializer.start(this, * services.toTypedArray(), apiKey = "<App-key>")
The specific point in the lifecycle of the application where the download of the theme takes place is up to the application. But if you want the theme to take effect immediately on the onboarding or restore flows, the application should make sure it has been downloaded to the client side and apply it before starting the flows.
The downloadTheme
function needs to be passed the SAP Mobile Services Endpoint URL as well as the App-ID
. It returns an array of the downloaded files. Note that the downloadTheme
function uses a delta token so that the function returns “304 Not Modified” if the theme has not been updated in SAP Mobile Services since the last download.
The resulting code then can look like this:
private fun updateTheme(launchScreen: LaunchScreen) {
val serviceUrl = "<mobileServicesURL>”
val app_id = "<App-ID>"
MainScope().launch {
when (val result = getService(ThemeDownloadService::class)?.downloadTheme(serviceUrl, app_id)) {
is ServiceResult.SUCCESS -> {
Log.i("Theme Download", "Theme is downloaded to the local.")
checkLogo(launchScreen)
checkTheme()
}
is ServiceResult.FAILURE -> {
if(!("THEME_DOWNLOAD_SERVICE_THEME_NOT_MODIFIED".equals(result?.code?.name)) {
val message: String = result.message
DialogHelper(this@WelcomeActivity).showOKOnlyDialog(
fragmentManager = fManager,
message = message
)
Log.e("Theme Download", "Theme download failed with error message: $message")
}
}
}
}
}
Applying a Custom Theme¶
ThemeDownloadService
provides the APIs required to get a theme file and logos:
getThemeFile()
to get the theme file in JSON formatgetLightLogo()
to get the logo image for a light themegetDarkLogo()
to get the logo image for a dark theme
Regarding custom logos, there is no SDK logic currently available to apply them to the application. Most of the screens currently in the SDK assume that the logo is in the application resources (i.e., bundled into the app at build time), so there is no easy way to apply it broadly in this case. It is up to the application to use them where appropriate.
The sample code assumes that you define a WelcomeActivity
screen with com.sap.cloud.mobile.fiori.onboarding.LaunchScreen
:
import com.sap.cloud.mobile.foundation.mobileservices.SDKInitializer.getService
...
private val logos: Pair<Bitmap?, Bitmap?> ? by lazy {
var lightLogo:Bitmap? = null
var darkLogo:Bitmap? = null
var options = BitmapFactory.Options()
options.inJustDecodeBounds = false
getService(ThemeDownloadService::class)?.let { service ->
service.getLightLogo()?.also { logo -> lightLogo = BitmapFactory.decodeFile(logo.path, options)}
}
getService(ThemeDownloadService::class)?.let { service ->
service.getDarkLogo()?.also { logo -> darkLogo = BitmapFactory.decodeFile(logo.path, options)}
}
return@lazy Pair(lightLogo, darkLogo)
}
private fun checkLogo(launchScreen: LaunchScreen) {
var isDarkMode = (application.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
var logoFile : Bitmap?
if (isDarkMode){
logoFile = logos?.second
} else {
logoFile = logos?.first
}
logoFile?.let {
var launchScreenImages = launchScreen.findViewById<ImageView>(R.id.launchscreen_sapLogo)
launchScreenImages.setImageBitmap(it)
}
}
private fun checkTheme() {
val themeFile = getService(ThemeDownloadService::class)?.let { it.getThemeFile()}
themeFile?.let {
if(it.exists()) {
val colorMapper = CustomColorMapper(application.applicationContext)
var inputStream = it.inputStream()
colorMapper.updateColors(inputStream)
}
}
}
Limitations¶
Please note that:
- Themes need to be created based on the SAP Horizon Morning or Horizon Evening themes.
- Not every control property that may be themed in the UI Theme Designer has a mobile control equivalent. That is why not all controls may have the correct theming applied. The mapping may be refined by the developer to add missing properties. The manually changed
themes.json
file may be uploaded to mobile services to update the respective theme. - For Android API Level 30 or above is required.