56

So I'm coming down to release-time for my application. We plan on releasing two versions, a free ad-based play-to-unlock version, and a paid fully unlocked version. I have the code set up that I can simply set a flag on startup to enable/disable ads and lock/unlock all the features. So literally only one line of code will execute differently between these versions.

In order to release two separate applications, they require different package names, so my question is this: Is there an easy way to refactor my application's package name? Eclipse's refactoring tool doesn't resolve the generated R file, or any XML references in layout and manifest files. I've attempted to make a new project using the original as source, but I can't reference the assets and resources, and I'm looking to avoid duplicating any of my code and assets. It's not a huge pain to refactor it manually, but I feel there must be a better way to do it. Anybody have an elegant solution to this?

Edit/Answered:

For my situation I find it perfectly acceptable to just use Project -> Android Tools -> Rename Application Package. I wasn't aware this existed, and I feel like an idiot for posting this now. Thanks for everyone's answers and comments, feel free to vote this closed.

LeffelMania
  • 12,765
  • 4
  • 32
  • 34
  • I have written a package refactor utility for my Maven projects: http://goo.gl/aaGqf, it will change all package name occurrences in all text files. But it depends on other utils of mine, so it's not very easy to install. However, are you really need to rename the package names? I don't think so. To protect your free version from being cracked, it's valuable to use some kind of code obfuscators, ProGuard is a good one, it's very customizable and can be used in Ant build.xml. It will refactor your package names as well, even flatten them. – Lenik Apr 08 '11 at 04:15

7 Answers7

36

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

Denis
  • 519
  • 2
  • 5
  • 12
17

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.

Community
  • 1
  • 1
mmeyer
  • 3,598
  • 1
  • 19
  • 22
  • If I make my current project a Library, I will then create 2 additional projects that basically just have Manifest files with the library activities, yes? The assets/resources from the Library project will be bundled and accessible in the new project shells? – LeffelMania Apr 08 '11 at 04:19
  • 1
    Yes, anything under /res will get merged. Which is kinda cool in that you can have some default value in the library project and then define the same resource in some of the consuming projects when you want something different. I dont think the assets merge and you'll have to copy them to the other projects. Only the actual (non-library) projects will need activities referenced in their manifest. The library projects manifest will be nearly empty. – mmeyer Apr 08 '11 at 06:38
  • The activities must be declared in the Libarary manifest? (http://developer.android.com/guide/developing/projects/projects-eclipse.html) AND in both free and pro Android Projects? I'm confused on this point... – ycomp Feb 23 '12 at 13:35
  • 1
    My experience is that the activities only need declared in the manifest of the projects that can actually be run as an apk (free and pro in this example). In this way you can implement diff activities in lib and actually only use the ones you need for the certain edition. Note as a matter of habit, I maintain the manifest for the library project to include all activities and permissions and such to serve as a reference when/if I make a new project that depends on the library. – mmeyer Feb 23 '12 at 22:12
  • The problem I encountered with this approach is that [the free and paid have cannot expose the same content providers](http://stackoverflow.com/questions/3306639/multiple-apps-use-same-content-provider) – rds Dec 09 '12 at 14:21
8

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.

galex
  • 593
  • 7
  • 10
  • FYI, you also have to add this to the top of your build.gradle file: import java.util.regex.Pattern – kenyee Jun 28 '13 at 22:21
  • Can this template concept also be applied to layout xml files, or do we just have to load up different layouts at runtime based on which version is running? How do I find out in the Java code which version is running (say, in an Activity)? – Ogre Aug 02 '13 at 01:44
  • This template concept also can be applied to resource files and java files. I have updated the sample above to show this. The sample shows a java file processing as well as manifest file. – galex Aug 02 '13 at 12:21
8

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"
    }
galex
  • 593
  • 7
  • 10
  • 2
    Please edit your other answer and put a huge warning on top with a link to this answer! – TWiStErRob Jul 08 '15 at 10:16
  • 2
    You can also use the same method to have resources with `resValue 'bool', 'is_paid', 'true'` and then use it like `@bool/is_paid`. – TWiStErRob Jul 08 '15 at 10:19
2

For everyone who want to use the solution by Denis:
In the new gradle version packageName is now applicationId and don't forget to put productFlavors { ... } in android { ... }

productFlavors {
    lite {
        applicationId = 'com.project.test.app'
        versionCode 1
        versionName '1.0.0'
    }
    pro {
        applicationId = 'com.project.testpro.app'
        versionCode 1
        versionName '1.0.0'
    }
}
Vader
  • 191
  • 8
0

If you want another application name, depending of the flavor, you can also add this:

productFlavors {
    lite {
        applicationId = 'com.project.test.app'
        resValue "string", "app_name", "test lite"
        versionCode 1
        versionName '1.0.0'
    }
    pro {
        applicationId = 'com.project.testpro.app'
        resValue "string", "app_name", "test pro"
        versionCode 1
        versionName '1.0.0'
    }
}
luguen
  • 1
0

One approach I'm experimenting with is using fully-qualified names for activities, and just changing the package attribute. It avoids any real refactoring (1 file copy, 1 text sub).

This almost works, but the generated R class isn't picked up, as the package for this is pulled out of AndroidManifest.xml, so ends up in the new package.

I think it should be fairly straight forward to build AndroidManifest.xml via an Ant rule (in -pre-build) that inserts the distribution package name, and then (in -pre-compile) the generated resources into the default (Java) package.

Hope this helps,

Phil Lello

Phil Lello
  • 8,377
  • 2
  • 25
  • 34
  • I think there's a fairly simple solution to this - see http://stackoverflow.com/questions/3711967/best-way-to-have-paid-and-free-version-of-an-android-app/13111546#13111546 – Neil Townsend Feb 21 '13 at 16:49