ChatGPT解决这个技术问题 Extra ChatGPT

Using Build Flavors - Structuring source folders and build.gradle correctly

Please note: Answer edited after Xavier's Answer

I am trying to use different Build Flavors for one same Application project in Android Studio. However, I seem to be having a terrible time configuring it to work appropriately.

Steps:

Create a new Android Studio Project, named 'Test'. Open build.gradle* and added the following lines: productFlavors { flavor1 { packageName 'com.android.studio.test.flavor1' } flavor2 { packageName 'com.android.studio.test.flavor2' } } After restarting Android Studio, I now see 4 build variants under the Build Variants section. Meaning we were succesful on seting up the product flavors so far. ** Created a new Source folder for flavor1; however, I am not sure if I'm doing it the right way. Here's how I did it: Keep in mind that my Package name for this project is: com.foo.test Right click on the src folder, for flavor1, I actually created the individual folders in the explorer, in a way that the structure is src/flavor1/java/com/foo/test/MainActivity.java. The above worked well, since the 'java' folder is in blue, meaning the IDE knows its an active source directory. Also, the package was automatically created. Despite of this, I am getting a warning for duplicate class found. See screenshot here. For flavor2, I tried creating the package manually, but 'src' folder for flavor2 seems not be in blue, and therefore the options are different when right clicked, and 'New Package' is not available for me to use. See image here. Note that for flavor1, I also created a 'res' directory, which does turn blue, but despite of that, doesn't offer the ability to create either an Android Resource file, or Andorid resource directory, in case I wanted to use different resoruces for different flavors.

Am I doing something wrong? Or am I missing something? Let me know if you need more info.

*My Project seems to have two build.gradle files. One located on the root of the project folder (\GradleTest), this one is empty. The second one located on the root of a subfolder of \GradleTest, also labeled 'GradleTest' (GradleTest-GradleTest), this is the one that already had code when opened; therefore, that is the one I edited.

** I checked gradle settings and apparently Use auto-import was already enabled. Despite of this, making changes to the build.gradle file doesn't automatically update the build variants. Note: I also tried using Build - Rebuild Project, and/or Build - Make Project, no-go. I still have to close the project, and re-open for changes to take effect.

Note that applicationId is now the supported instead of packageName.

X
Xavier Ducrohet

If you got in the Studio preferences, under the Gradle section, you can enable auto-import for your project (we'll enable this by default later). This will let Studio re-import your build.gradle whenever you edit it.

Creating flavors doesn't mean you're going to use custom code for them so we don't create the folders. You do need to create them yourself.

If you look at my IO talk you'll see how we mix in together values from the flavors and build type to create the variant.

For the Java source:

src/main/java
src/flavor1/java
src/debug/java

are all 3 used to create a single output. This means they can't define the same class.

If you want to have a different version of the same class in the two flavor you'll need to create it in both flavors.

src/flavor1/java/com/foo/A.java
src/flavor2/java/com/foo/A.java

And then your code in src/main/java can do

import com.foo.A

depending on the flavor selected, the right version of com.foo.A is used.

This also means both version of A must have the same API (at least when it comes to the API used by classes in src/main/java/...

Edit to match revised question

Additionally, it's important to put the same A class only in source folders that are mutually exclusive. In this case src/flavor1/java and src/flavor2/java are never selected together, but main and flavor1 are.

If you want to provide a different version of an activity in different flavor do not put it in src/main/java.

Do note that if you had 3 flavors and only wanted a custom one for flavor1, while flavor2 and flavor3 shared the same activity you could create a common source folders for those two other activities. You have total flexibility in creating new source folders and configuring the source set to use them.

On to your other points:

It's normal that the 2nd flavor source folder is not blue. You need to switch to the 2nd flavor to enable it, and then you'll be able to create packages and classes inside. Until then, Studio doesn't consider it to be a source folder. We'll hopefully improve this in the future to make the IDE aware of those unactive source folders.

I think it's also normal that you can't create resource files in the res folder. The menu system hasn't been updated to deal with all these extra resource folders. This will come later.


I added some new elements at the end of my answer, but the duplicate make sense. You can't have the same class in both src/main/java and src/flavor1/java as both are used when selecting flavor1. In my answer, notice how I put the same class in only flavor1/java and flavor2/java as these are exclusive and never enabled together.
Hey Xavier, can you give me a more detailed description of how I can use a different version of an activity in my flavors? I have a test project where I want to use different versions of my MainActivity, but in both apks (flavor1 and flavor2) there is only the version of main/java. When I don't put MainActivity inside main/java, the app crashes when I start it.
@XavierDucrohet how about having different resources as well as different code based on flavors, but have them in different modules so that we can include one module or the other based on the flavor, without having to mix code and resources in the same root project? Is that supported?
@ValerioSantinelli You can do per-flavor dependencies. Use flavorCompile ...
@XavierDucrohet I tried what you suggested but it's not working as I expected. You can see how my project is structured there: stackoverflow.com/q/24410995/443136
A
A-Droid Tech

"Product Flavors" on Android

I've been asked sometimes on how to work with different hosts, icons, or even package names, deppending on different versions of the same app.

There are lot of reasons to do this and one easy way to go: Product Flavors.

You can define on your build.gradle script these kind of things I've described before.

Product flavors Part of this article is written thinking on product flavors, so, what are they? Regarding to Android documentation:

A product flavor defines a customized version of the application build by the project. A single project can have different flavors which change the generated application.

How can you define them? You must write on your build.gradle which flavors you want to define:

productFlavors {  
        ...
        devel {
            ...
        }

        prod {
            ...
        }
    }

Now, we will have two different flavors of our app. You can check it on Android Studio too inside the Build Variants tab

Build variants

Multiple package names

What if you want to have installed on your phone one app with development state and one for production state. As you might know, you can only install one app with the same package name (if you try to install some new APK with the same as one that's installed on your phone, it will try to update it).

The only thing you have to do is to define it on each of your product flavors:

android {  
    productFlavors {
        devel {
            applicationId "zuul.com.android.devel"
        }
        prod {
            applicationId "zuul.com.android"
        }
    }
}

Send requests to multiple hosts depending on the flavor As before, you must include some params on your product flavor config field.

android {  
    productFlavors {
        devel {
            applicationId "zuul.com.android.devel"
            buildConfigField 'String', 'HOST', '"http://192.168.1.34:3000"'

        }

        prod {
            applicationId "zuul.com.android"
               buildConfigField 'String', 'HOST', '"http://api.zuul.com"'

        }
    }
}

As an example, we will try to show you how you can integrate this with Retrofit to send request to the appropiate server without handling which server you're pointing and based on the flavor. In this case this is an excerpt of the Zuul android app:

public class RetrofitModule {

    public ZuulService getRestAdapter() {
        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(BuildConfig.HOST)
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .build();
        return restAdapter.create(ZuulService.class);
    }

}

As you can see you just have to use the BuildConfigclass to access the variable you've just defined.

Any variable available through your code The HOST variable is not the only one you can expose in your code. You can do it with whatever you want:

prod {  
    applicationId "zuul.com.android"
    buildConfigField 'String', 'HOST', '"http://api.zuul.com"'
    buildConfigField 'String', 'FLAVOR', '"prod"'
    buildConfigField "boolean", "REPORT_CRASHES", "true"
}

You can access them as follows:

BuildConfig.HOST  
BuildConfig.FLAVOR  
BuildConfig.REPORT_CRASHES  

Different icons per flavor If you want to have different icons per flavor, so you can visually detect which one you're opening (you could do it by the name too... But it could not fit the space!), you just have to define new directory structures for each of the flavors.

In the example I've just used there are two flavors: devel and prod. Then, we could define an two new directory structures so we can define the resources we want:

structure

This works with other types of resources like strings.xml, integers.xml, arrays.xml, etc.

Configure Signing Settings

To manually configure the signing configurations for your release build type using Gradle build configurations:

1.Create a keystore. A keystore is a binary file that contains a set of private keys. You must keep your keystore in a safe and secure place. 2.Create a private key. A private key represents the entity to be identified with the app, such as a person or a company. 3.Add the signing configuration to the module-level build.gradle file:

android {
...
defaultConfig {...}
signingConfigs {
    release {
        storeFile file("myreleasekey.keystore")
        storePassword "password"
        keyAlias "MyReleaseKey"
        keyPassword "password"
    }
}
buildTypes {
    release {
        ...
        signingConfig signingConfigs.release
    }
}

}

Generate a signed APK:

To generate a signed APK, select Build > Generate Signed APK from the main menu. The package in app/build/apk/app-release.apk is now signed with your release key.

ref: https://developer.android.com/studio/build/build-variants.html#signing,http://blog.brainattica.com/how-to-work-with-flavours-on-android/


b
ben75

It seems you need to reload your project after adding new flavors in build.gradle. After that, you will see 4 build Variants in the Build Variants view (you access it from the left edge of the window).

Regarding the additional source directories, it seems you need to create them by hand : src/flavor1/java and src/flavor2/java. You will see that changing the flavor in the "Build Variants" view will change the currently active source directories (directory is blue when it is an active source directory)

Finally, "gradle will create new sourceSets for your new flavors" means that gradle will create the objects android.sourceSets.flavor1 and android.sourceSets.flavor2 and you can use them in your build.gradle script. But those objects are created dynamically, that's why you don't see them in the build.gradle (I suggest you reading this : http://www.gradle.org/docs/current/userguide/tutorial_using_tasks.html Especially the 6.6: it explain the creation of dynamic task. A gradle script is a groovy script, so I suggest you to get familiar with groovy too)


I think the import note is the Build Variants View, i didn't notice that.
T
Tomer

I had the same issue when I migrated my project to Gradle. The problem was that the build didn't find the proper resources folder. I fixed it by adding this under the android element in build.gradle:

sourceSets {
        main {
            res.srcDirs = ['myProject/res']
        }
    }

b
bitrock

Something that is important and blocked me for quite awhile is that the flavor name that needs to match the package as opposed to the package defined inside the flavor definition in gradle. For example:

src/flavor1/java/com/foo/A.java

will match

productFlavors {
  flavor1 {
    packageName 'com.android.studio.test.foobar'
  }
}

but

src/foobar/java/com/foo/A.java will not be used for the flavor1 build.


C
Corey Rodgers

After configuring flavors in your gradle, in order to allow react-native to look at the custom .env files in the root of your project folder you must add to android/app/build.gradle

In my example I am creating two apps for the android device using the same codebase, one for group1 and one for group2

//add this to line 3 of your app/build.gradle

project.ext.envConfigFiles = [
projecta: ".env.development.android.projecta",
projectb: ".env.development.android.projectb",
]

//don't forget to add

android {
compileSdkVersion rootProject.ext.compileSdkVersion
flavorDimensions “default” // add this line
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8

}

and then match up the keys from this array to your product flavors

  productFlavors {
    project1 {
        minSdkVersion rootProject.ext.minSdkVersion
        applicationId 'com.nativeapp.project1'
        targetSdkVersion rootProject.ext.targetSdkVersion
        resValue "string", "build_config_package", "com.nativeapp"
    }
    project2 {
        minSdkVersion rootProject.ext.minSdkVersion
        applicationId 'com.nativeapp.staff'
        targetSdkVersion rootProject.ext.targetSdkVersion
        resValue "string", "build_config_package", "com.nativeapp"
    }
}

also make sure that the applicationId and resValue start with your apps package name

in my case it's 'nativeapp' but you can find yours found in the MainActivity.java file.

you can find this if you dig into android/app/main/java/com/ directory.

After this you can then configure your scripts when needed to look like this

"scripts": {
"android:project1": "ENVFILE=.env.development.candidate react-native run-android --variant=projectaDebug --appIdSuffix=projecta",
"android:staff": "ENVFILE=.env.development.staff.android react-native run-android --variant=projectbDebug --appIdSuffix=projectb",}

Note: when adding scripts, the variant must be the flavor name for example 'project2' followed by 'Debug' = 'projectb' as this is your default buildType

Also make sure you've got the relevant folders created in android/app/src/ for each flavour.

in my case

android/app/src/projecta/
android/app/src/projecta/

if all set up correctly, when you build using the scripts you've created it should create the correct res folder and android manifest files.

If you want to change the name of each individual apps instance, change the strings.xml in the res/values directory.

<resources>
<string name="app_name">Project1</string> //this will be the name of your app when its built in your emulator
</resources>

T
TouchBoarder

In gradle:

For Build Types you only need:

buildTypes {
   release{
    //proguard, signing etc.
   }
   debug {
    //development
   }
  }
}

And then for flavors you add the ones you need

productFlavors {
    pro {
        applicationIdSuffix '.paid'
        buildConfigField 'boolean', 'PRO', 'true'
    }
    free {
        applicationIdSuffix '.free'
        buildConfigField 'boolean', 'PRO', 'false'
    }
}