Amplifying Flavor Customization with Kotlin Extensions: Crafting Tailored Experiences

YE MON KYAW
Arpalar Tech
Published in
6 min readAug 24, 2023

Kotlin stands out for its elegance, conciseness, and versatility. One of its most remarkable features is extensions, which empower developers to augment existing classes with new functionality without altering their source code.

Today I will show how we can use the Kotlin extension for different flavors, in our Android application. Here is an example of a simple compose app showing as “Android” when running

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.indexer.extenstionforeasy.ui.theme.ExtenstionForEasyTheme


class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ExtenstionForEasyTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Greeting("Android")
}
}
}
}
}


@Composable
fun Greeting(
name: String,
modifier: Modifier = Modifier
) {
Text(
text = "Hello $name!",
modifier = modifier
)
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
ExtenstionForEasyTheme {
Greeting("Android")
}
}

Now I want to add flavor and based on the flavor I want to show “Champagne” and “Melot” so I added the flavor in the application gradle as below

plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}

android {
namespace 'com.indexer.extenstionforeasy'
compileSdk 34

defaultConfig {
applicationId "com.indexer.extenstionforeasy"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}

flavorDimensions "wine"

productFlavors {
champagne {
dimension "wine"
applicationIdSuffix ".champagne"
}

melot {
dimension "wine"
applicationIdSuffix ".melot"
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}

sourceSets {
champagne {
java {
srcDirs 'src/champange/java'
}
}
melot {
java {
srcDirs 'src/melot/java'
}
}

}

buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.3.2'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}

dependencies {

implementation 'androidx.core:core-ktx:1.10.1'
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation 'androidx.activity:activity-compose:1.7.2'
implementation platform('androidx.compose:compose-bom:2022.10.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
}

Now we have two new flavors champagne and merlot as you in below.

Flavoring allows for the creation of distinct versions of an app tailored to different scenarios, such as countries, features, or environments.

While flavors bring numerous benefits, managing them without the aid of extensions can introduce challenges that impact code maintainability, readability, and overall development efficiency.

1. Code Duplication: Without extensions, developers often resort to duplicating code across different flavors to accommodate varying requirements. This duplication can lead to increased maintenance efforts and the risk of inconsistencies or bugs when changes are made in one flavor but not propagated to others.

2. Reduced Readability: Lacking the encapsulation that extensions provide, code becomes more verbose and less readable. Multiple if-else conditions or separate classes may be used to handle different flavors, resulting in convoluted code that is harder to understand and debug.

3. Maintenance Complexity: The absence of extensions can amplify the complexity of maintaining multiple flavor-specific implementations. Updating a common piece of functionality requires modifications across different parts of the codebase, increasing the likelihood of introducing errors.

4. Inconsistent Naming Conventions: Without extensions, developers might create separate classes or methods for each flavor, which can lead to inconsistencies in naming conventions. This inconsistency can make it difficult for developers to predict where to find specific functionality within the codebase.

5. Long Build Times: In cases where flavors are managed without extensions, build times can be negatively impacted due to duplicate code and resources being compiled separately for each flavor. This can lead to longer compilation times, slowing down the development process.

6. Collaboration Challenges: Collaborating with a team on a project with multiple flavors, but lacking extensions, can be challenging. Team members may need to navigate through disparate parts of the codebase, leading to communication gaps and difficulties in understanding the project’s structure.

7. Prone to Errors: Handling flavor-specific logic without extensions increases the likelihood of introducing errors or omitting necessary functionality. Manual replication of code across flavors can lead to inconsistencies and difficult-to-trace bugs.

8. Inefficiency in Resource Management: Flavors often involve different resources such as layouts, strings, and assets. Managing these resources without extensions can result in redundant files, making it harder to keep track of which resources are shared and which are flavor-specific.

9. Limited Reusability: Without extensions, the lack of a consistent approach for handling flavor-specific functionality can limit code reusability. Developers may avoid reusing code if it requires significant modifications for each flavor.

10. Difficulty in Scaling: As the app evolves and more flavors are added, the challenges of managing flavors without extensions can become increasingly pronounced. Scaling up the project becomes more complex and time-consuming.

so let me refactor with the activity as below and add an extension

//In main app i am update get string title instead of hard code
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ExtenstionForEasyTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Greeting(getStringTitle())
}
}
}
}
}
//That is title for Champagne flavor
internal fun MainActivity.getStringTitle(): String {
return "Champagne"
}

//That is title for Melot flavor
internal fun MainActivity.getStringTitle(): String {
return "Melot"
}

Now you notice we did not need another activity in flavor and reduced the duplication of code, Also our tests are unique between different flavors. here is the benefit of using an extension

Advantages of Kotlin Extensions:

  1. Enhanced Readability: Extensions allow developers to encapsulate logic within a specific type, making the codebase more intuitive and self-explanatory. This improves code readability and comprehension, especially for domain-specific operations.
  2. Encapsulation and Separation of Concerns: Extensions enable a cleaner separation of concerns. Related functionality can be encapsulated within extensions, reducing the need for utility classes or scattered code snippets. This contributes to a more modular and organized codebase.
  3. Compatibility and Adaptability: One of the key advantages of extensions is their compatibility with external classes. Developers can enrich classes from libraries or APIs without modifying the original source code. This adaptability is invaluable for integrating third-party libraries seamlessly.
  4. Maintainability: Kotlin extensions decouple the extended functionality from the core class, minimizing the risk of introducing bugs when upgrading libraries or changing dependencies. This leads to greater code stability and easier maintenance.
  5. Code Reusability: Extensions foster code reuse by allowing developers to define functions that can be utilized across various parts of the application. This reduces redundancy and promotes a more efficient development process.
  6. Improved Collaboration: Extensions lead to more standardized and consistent code. When used in a team setting, they can simplify collaboration by establishing common patterns and reducing the learning curve for new team members.
  7. Less Boilerplate: Kotlin extensions help reduce boilerplate code by abstracting common operations. This leads to shorter, more focused code snippets, which ultimately speeds up development and minimizes the potential for errors.
  8. Tailored Functionality: Extensions enable developers to customize classes to better fit project requirements. They can add convenience methods, validation checks, or any other features that align with specific use cases.

Now already in 2023, Are you still creating duplicate activity in different flavors, let's move the way to be cleaner.

--

--

YE MON KYAW
Arpalar Tech

Software Engineer who write code in Kotlin / Android