Requirement Description
In multi‑variant application development (e.g., free vs. paid, debug vs. release), it's essential to manage both source code and resources/assets (images, strings, layouts) across variants. The goal is to:
- Create a flexible build system where shared code and variant‑specific overrides (assets or code) can coexist with minimal duplication.
- For resources, use directory structures that allow automatic resource picking at build time without runtime branching.
- For source code, use conditional compilation or flagging to ensure variant‑specific logic and behavior while maintaining a clean codebase.
Background Knowledge
- Managing Modules: Organizing your project into modules (entry, feature, common) to separate shared and variant-specific code.
-
Configuring Multiple Targets and Products: In
build‑profile.json5, you defineproducts(variants) andtargetsto customize the build for each variant (e.g.,free_debug,paid_release). -
Conditional Source Flags: Using
buildProfileFieldsto set flags that allow conditional logic based on the variant. - Generating the BuildProfile Class File
Implementation Steps
1. Define Modules & Variants
Split the project into modules (e.g.,
commonLib,entry) and define build targets for each variant inbuild‑profile.json5.In the root
build‑profile.json5, define products and their corresponding build configurations (e.g., free/paid, debug/release). Each product points to different targets.Example:
"targets": [
{
"name": "default",
"applyToProducts": [ "default" ]
},
{
"name": "free",
"applyToProducts": [ "free" ]
},
{
"name": "paid",
"applyToProducts": [ "paid" ]
}
]
2. Organize Resource Folders for Variants
- Use a resource folder structure that supports variant overrides, such as:
AppScope
├───common
│ └───media
│ └─── image.png
├───freeResources
│ └───media
│ └─── image.png
└───paidResources
└───media
└─── image.png
In the
build‑profile.json5for each module, map the appropriate resource directories for each target. HarmonyOS supports this and resolves the correct resource automatically.Example for a module:
"products": [
{
"name": "free",
"resource" : {
"directories" : [
"./AppScope/freeResources"
]
}
},
{
"name": "paid",
"resource" : {
"directories" : [
"./AppScope/paidResources"
]
}
}
]
- When building the "paid" variant, the build system will pick
image.pngfrompaid/, and if not available, it will fallback tocommon/.
3. Handle Source Code Management (Conditional Compilation)
For source code, you can conditionally compile or execute variant-specific logic using flags set in
build‑profile.json5(e.g.,IS_PREMIUM). You can then use these flags in your code to determine which logic or functionality to enable based on the variant.Use these flags in source code to adjust logic:
import BuildProfile from "BuildProfile"
@Entry
@Component
struct Index {
public target = BuildProfile.TARGET_NAME
@Builder
private setupPremiumFeatures() {
Text("Premium")
}
@Builder
private setupFreeFeatures() {
Text("Free")
}
build() {
Column() {
if (this.target === 'paid') {
this.setupPremiumFeatures();
} else {
this.setupFreeFeatures();
}
}
.width('100%')
.height('100%')
}
}
4. Asset and Source Override Workflow
- For shared resources, update the common resource folder.
- For variant‑specific changes, place assets in the respective variant folders (e.g.,
free/,paid/). - For source code, conditionally compile based on
buildProfileFields. Useif/elsefor runtime decisions if variants cannot be handled by the build system alone. - Example:
-
Shared assets:
common/logo.pngis used across all variants unless overridden bypaid/logo.png. -
Conditional code: Different API endpoints for paid and free users, determined by
BuildProfile.TARGET_NAME.
-
Shared assets:
5. Create BuildProfile class
Select the module for build and choose Build > Generate Build Profile ${moduleName} on the menu bar.
Code Snippet / Configuration
Project-level build‑profile.json5 :
{
"app": {
"signingConfigs": [],
"products": [
{
"name": "default",
"signingConfig": "default",
"targetSdkVersion": "6.0.0(20)",
"compatibleSdkVersion": "6.0.0(20)",
"runtimeOS":"HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
},
{
"name": "free",
"signingConfig": "default",
"targetSdkVersion": "6.0.0(20)",
"compatibleSdkVersion": "6.0.0(20)",
"runtimeOS":"HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
},
"resource" : {
"directories" : [
"./AppScope/freeResources"
]
}
},
{
"name": "paid",
"signingConfig": "default",
"targetSdkVersion": "6.0.0(20)",
"compatibleSdkVersion": "6.0.0(20)",
"runtimeOS":"HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
},
"resource" : {
"directories" : [
"./AppScope/paidResources"
]
}
}
],
"buildModeSet": [
{
"name": "debug",
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
},
{
"name": "free",
"applyToProducts": [
"free"
]
},
{
"name": "paid",
"applyToProducts": [
"paid"
]
}
]
}]
}
Module-level entry/build‑profile.json5 (resource configuration):
{
"apiType": "stageMode",
"buildOption": {
"resOptions": {
"copyCodeResource": {
"enable": false
}
}
},
"buildOptionSet": [
{
"name": "free",
"arkOptions": {
"buildProfileFields": {
"IS_PREMIUM": false
},
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
{
"name": "paid",
"arkOptions": {
"buildProfileFields": {
"IS_PREMIUM": true
},
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
}
],
"targets": [
{
"name": "free"
},
{
"name": "paid"
}
]
}
Asset Directory Structure :
AppScope
├───freeResources
│ └───media
│ └─── image.png
└───paidResources
└───media
└─── image.png
ArkTS Source Code with Conditional Compilation and Resource Handling:
import BuildProfile from "BuildProfile"
@Entry
@Component
struct Index {
public target = BuildProfile.TARGET_NAME
public img = this.getUIContext().getHostContext()?.resourceManager.getMediaContent($r('app.media.image').id)
@Builder
private setupPremiumFeatures() {
Text("Premium")
}
@Builder
private setupFreeFeatures() {
Text("Free")
}
build() {
Column() {
if (this.target === 'paid') {
this.setupPremiumFeatures();
} else {
this.setupFreeFeatures();
}
Image($r('app.media.image'))
.width(100)
.height(100)
.objectFit(ImageFit.Contain)
}
.width('100%')
.height('100%')
}
}
Test Results
- Select product:
- Check that no unintended resources are included, and the right source code paths are compiled.
- Asset Validation: Ensure that variant‑specific assets (e.g., images) are correctly selected based on the variant.
-
Source Validation: Ensure that the correct logic is being executed based on flags (
BuildProfile.TARG_NAME).
Limitations or Considerations
This sample supports API Version 12 Release and above.
This sample supports HarmonyOS 5.0.0 Release SDK and above.
Compilation and execution require DevEco Studio 5.0.0 Release or later
Top comments (0)