Create Free/Paid versions of Application from same code

Solution 1:

It's very simple by using build.gradle in Android Studio. Read about productFlavors. It is a very usefull feature. Just simply add following lines in build.gradle:

productFlavors {
    lite {
        packageName = 'com.project.test.app'
        versionCode 1
        versionName '1.0.0'
    }
    pro {
        packageName = 'com.project.testpro.app'
        versionCode 1
        versionName '1.0.0'
    }
}

In this example I add two product flavors: first for lite version and second for full version. Each version has his own versionCode and versionName (for Google Play publication).

In code just check BuildConfig.FLAVOR:

if (BuildConfig.FLAVOR == "lite") {
   // add some ads or restrict functionallity

}

For running and testing on device use "Build Variants" tab in Android Studio to switch between versions: enter image description here

Solution 2:

Possibly a duplicate of Bulk Publishing of Android Apps.

Android Library projects will do this for you nicely. You'll end up with 1 library project and then a project for each edition (free/full) with those really just containing different resources like app icons and different manifests, which is where the package name will be varied.

Hope that helps. It has worked well for me.

Solution 3:

The best way is to use "Android Studio" -> gradle.build -> [productFlavors + generate manifest file from template]. This combination allows to build free/paid versions and bunch of editions for different app markets from one source.


This is a part of templated manifest file:


<manifest android:versionCode="1" android:versionName="1" package="com.example.product" xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@drawable/ic_launcher"  
    android:label="@string/{f:FREE}app_name_free{/f}{f:PAID}app_name_paid{/f}"
    android:name=".ApplicationMain" android:theme="@style/AppTheme">
    <activity android:label="@string/{f:FREE}app_name_free{/f}{f:PAID}app_name_paid{/f}" android:name=".ActivityMain">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
</application>

This is template "ProductInfo.template" for java file: ProductInfo.java


    package com.packagename.generated;
    import com.packagename.R;
    public class ProductInfo {
        public static final boolean mIsPaidVersion = {f:PAID}true{/f}{f:FREE}false{/f};
        public static final int mAppNameId = R.string.app_name_{f:PAID}paid{/f}{f:FREE}free{/f};
        public static final boolean mIsDebug = {$DEBUG};
    }

This manifest is processed by gradle.build script with productFlavors and processManifest task hook:


import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction  
...

android {
    ...
    productFlavors {
        free {
            packageName 'com.example.product.free'
        }
        paid {
            packageName 'com.example.product.paid'
        }
    }
    ...
}

afterEvaluate { project ->
    android.applicationVariants.each { variant ->

        def flavor = variant.productFlavors[0].name

        tasks['prepare' + variant.name + 'Dependencies'].doLast {
            println "Generate java files..."

            //Copy templated and processed by build system manifest file to filtered_manifests forder
            def productInfoPath = "${projectDir}/some_sourcs_path/generated/"
            copy {
                from(productInfoPath)
                into(productInfoPath)
                include('ProductInfo.template')
                rename('ProductInfo.template', 'ProductInfo.java')
            }

            tasks.create(name: variant.name + 'ProcessProductInfoJavaFile', type: processTemplateFile) {
                templateFilePath = productInfoPath + "ProductInfo.java"
                flavorName = flavor
                buildTypeName = variant.buildType.name
            }   
            tasks[variant.name + 'ProcessProductInfoJavaFile'].execute()
        }

        variant.processManifest.doLast {
            println "Customization manifest file..."

            // Copy templated and processed by build system manifest file to filtered_manifests forder
            copy {
                from("${buildDir}/manifests") {
                    include "${variant.dirName}/AndroidManifest.xml"
                }
                into("${buildDir}/filtered_manifests")
            }

            tasks.create(name: variant.name + 'ProcessManifestFile', type: processTemplateFile) {
                templateFilePath = "${buildDir}/filtered_manifests/${variant.dirName}/AndroidManifest.xml"
                flavorName = flavor
                buildTypeName = variant.buildType.name
            }
            tasks[variant.name + 'ProcessManifestFile'].execute()

        }
        variant.processResources.manifestFile = file("${buildDir}/filtered_manifests/${variant.dirName}/AndroidManifest.xml")
    }
}

This is separated task to process file


class processTemplateFile extends DefaultTask {
    def String templateFilePath = ""
    def String flavorName = ""
    def String buildTypeName = ""

    @TaskAction
    void run() {
        println templateFilePath

        // Load file to memory
        def fileObj = project.file(templateFilePath)
        def content = fileObj.getText()

        // Flavor. Find "{f:<flavor_name>}...{/f}" pattern and leave only "<flavor_name>==flavor"
        def patternAttribute = Pattern.compile("\\{f:((?!${flavorName.toUpperCase()})).*?\\{/f\\}",Pattern.DOTALL);
        content = patternAttribute.matcher(content).replaceAll("");

        def pattern = Pattern.compile("\\{f:.*?\\}");
        content = pattern.matcher(content).replaceAll("");
        pattern = Pattern.compile("\\{/f\\}");
        content = pattern.matcher(content).replaceAll("");

        // Build. Find "{$DEBUG}" pattern and replace with "true"/"false"
        pattern = Pattern.compile("\\{\\\$DEBUG\\}", Pattern.DOTALL);
        if (buildTypeName == "debug"){ 
            content = pattern.matcher(content).replaceAll("true");
        }
        else{
            content = pattern.matcher(content).replaceAll("false");
        }

        // Save processed manifest file
        fileObj.write(content)
    }
}

Updated: processTemplateFile created for code reusing purposes.

Solution 4:

Gradle allows to use generated BuildConfig.java to pass some data to code.

productFlavors {
    paid {
        packageName "com.simple.paid"
        buildConfigField 'boolean', 'PAID', 'true'
        buildConfigField "int", "THING_ONE", "1"
    }
    free {
        packageName "com.simple.free"
        buildConfigField 'boolean', 'PAID', 'false'
        buildConfigField "int", "THING_ONE", "0"
    }