Lets imagine the following case:
I'm building an application for cars, lets say VW. Now Skoda (belongs to the same group) wants the very same application but with different ressources files, and some checks need to be made into the code like. If VW, call this webservice, else if Skoda, call this other one.
I can't really answer to this question: "Is it the same application?"
Yes from the code point of view but no from the play store point of view...
How can I manage to have the same code base but with two different packages name?
I think I am in the good track reading this http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants but if you have any tricks, advice or help to share I would be glad to hear :)
I know this question has been asked, but I'm looking for a solution with gradle & Android Studio.
Tx
Hello you can use product flavor to define the package name for each new application as #stoke said
productFlavors {
app1 {
applicationId "com.example.package.app1"
buildConfigField "String", "API_URL", "\"https://www.myapi.com/apiv1/\""
}
app2 {
applicationId "com.example.package.app2"
buildConfigField "String", "API_URL","\"https://www.myapi.com/apiv2/\""
}
}
For defining different endpoints or url in your builds you can define build variable to use in your code.
Then you can access them like BuildConfig.API_URL in your java code.
For different resources for diferent flavors you need to define 2 directories that match the new flavors names.
Check this for file sctructure and resources.
The id of the application used by googles's play store is defined in the Build.gradle file :
like
productFlavors {
vw {
applicationId = "com.example.my.pkg.vw"
}
skoda {
applicationId = "com.example.my.pkg.skoda"
}
The ressources folder simply needs to be created under the new flavor's auto generated folder. see http://www.vogella.com/tutorials/AndroidStudioTooling/article.html#androidstudio_creatingresourcefolder for more info
Related
I want to setup a variable SERVER_URL and it'll be switched between environments production, test, development.
What I want to do:
#Override
protected void onCreate(Bundle bundle) {
R.urls.SERVER_URL; // is it a valid approach using resources?
}
Is there a way to switch environments(dev, prod, test) without change the code?
What's the best approach to implement this behavior?
Is there a way to configure it in the playstore my variable(SERVER_URL) or must I implement only in code?
There are 2 ways you can do it:
1/ By string resource like you want
Add a resource file called secret_keys.xml or whatever name to separate it from other resources file. Put your keys, api endpoints here as the normal string resource, remember to add translatable="false" if you don't want to mess with localization.
Place that file in app/debug/res/values/. Create a new debug folder if it doesn't exist. Do the same for staging or release, Android will automatically use the folder with the same name as the build type.
2/ By properties files
Create 3 .properties files and put your keys inside:
HOST="http://api.blablabla.com"
CLIENT_ID="hahaha"
CLIENT_SECRET="hehehe"
Bind it to BuildConfig variable in your app build.gradle, do the same for other build types:
def getPropertiesFile = { path ->
Properties properties = new Properties()
properties.load(new FileInputStream(file(path)))
return properties
}
android {
...
buildTypes {
debug {
...
getPropertiesFile('./config/development.properties').each { p ->
buildConfigField 'String', p.key, p.value
}
}
...
}
}
In your app just call BuildConfig.HOST to get the string you want
UPDATE
Ignore these config files in .gitignore:
app/config
secret_keys.xml
You can use different approaches. Ideally you shouldn't change URL at Runtime to minimize the attack surface. This approach could have direct impact on your app's security.
If your target is to modify this URL without touching code, you can do can bind this value at Compile time. You can create application.properties file and modify this file for different target builds (dev,production,test). In your code, you can read the values from properties file instead of hardcoded value. You can place this file in your assets folder and apply necessary obfuscation. This way only the properties file will change and your app's security remains intact.
Another way would be provide this parameter at build time (when you execute gradlew command). You can add commandline parameters which would be added to BuildConfig. In your code, you can simply refer to URL by calling BuildConfig.SERVER_URL. You can follow this SO to achieve this.
In either case I would recommend you to bind this value at compile time.
I have two options how use the link for the application when I need to open the link in just simple browser.
Rather I save it in the strings.xml file as
<string name="link_openBrowser_calendar" translatable="false">/schedule/</string>
that looks quite ugly lets say or just to save it in the Constants.java file as
final static final String linkSchedule = "/schedule/";
In some cases I have a lot of such links, that require additional parameter
<string name="link_openBrowser_account_byId" translatable="false">
/accounts/account/?id=%1$s
</string>
or even more parameters
<string name="view_meeting_time_inOneDay_period">%1$s at \n%2$s – %3$s</string>
that is useful when part of the string need to be translated, but in the code it looks quite massive and hardly to read.
String time = mEvent.isAllDay() ? getString(R.string.view_meeting_time_inOneDay_allDay, isTodayStr)
: getString(R.string.view_meeting_time_inOneDay_period, isTodayStr, startStr, finishStr);
Which one of these methods is more productive in the ways of scalability, performance or just clearness? I believe at some point it matters where to store this links, while they are not used all the time and only need "on the go", rather I spend memory on storing them as static.
If you need text strings for your application with optional text styling and formatting, strings.xml is convenient. For instance, you can use it to translate your strings into other languages. Android takes a look at user preferences and selects the strings.xml file from the correct localization folder (values is the base one, values-xx stands for other languages). On the other hand, you should use a separate class (e.g., Constants.java) for other strings which you need to use in the global scope. Defining that kind of constant strings in Constants.java is the best practice.
I have found that it is much better to store the resource strings that represent links to API or server links in the .gradle file. The idea here is that during the development stage we want to test our API or whatever with debug/dev API links, so with setting it in the .gradle file we can create different strings for each build type.
android {
...
buildTypes {
release {
buildConfigField("String", "API_URL", "\"https://api.release\"")
// These values are defined only for the release build, which
// is typically used for full builds and continuous builds.
buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
resValue("string", "build_time", "${minutesSinceEpoch}")
...
}
debug {
buildConfigField("String", "API_URL", "\"https://api.debug\"")
// Use static values for incremental builds to ensure that
// resource files and BuildConfig aren't rebuilt with each run.
// If they were dynamic, they would prevent certain benefits of
// Instant Run as well as Gradle UP-TO-DATE checks.
buildConfigField("String", "BUILD_TIME", "\"0\"")
resValue("string", "build_time", "0")
}
}
}
...
Using build variant in Android we can support different flavors.
I need to develop an application where I am supporting different clients. Each client needs are a little different.
However, the basic data, network call class etc are same for all clients.
How can I ensure partial code of my application remains same for all flavors?
This will help in maintaining one repository for all common classes.
You need to understand how build variant works.
Each client needs are a little different is a vague statement
Imagine you have an application which has different screen's for different countries. But major functionalities are the same.
Now using build variants you can make different flavors
1) For country one : This will have screens designed specific to country one
2) For country two : This will have screens designed specific to country two
3) Common part : All the common business logic can be put under your common package
While project is build, the common part is considered and specific flavor too becomes part of flavorXX.apk
productFlavors {
employee {
applicationId "com.myapp.employee"
}
driver {
applicationId "com.myapp.driver"
}
asset {
applicationId "com.myapp.asset"
}
vehicle {
applicationId "com.myapp.vehicle"
}
}
sourceSets {
asset {
manifest.srcFile 'src/asset/AndroidManifest.xml'
}
driver {
manifest.srcFile 'src/driver/AndroidManifest.xml'
}
employee {
manifest.srcFile 'src/employee/AndroidManifest.xml'
}
vehicle {
manifest.srcFile 'src/vehicle/AndroidManifest.xml'
}
}
In the above example , I am having different flavors of same application. Inorder to split accordingly you need to understand which part of your app goes into specific flavor and which can be kept common. Go through below links for more details.
Understanding Product Flavors reference link 1
Understanding Product Flavors reference link 2
I have a project with about 30 product flavors. Some of these product flavors need to share code, without sharing it with most product flavors. Can I assign code to a flavor group?
1) Do you know how to create folders for a particular product flavor?
You can create a folder with sources or resources that will be used for a particular combination of flavors too. https://developer.android.com/studio/build/build-variants.html#sourcesets
For example, we have flavors: "beta", "prod" in one dimension and "newApi", "oldApi" in another. We use one class implementation for all the flavor combinations except when it is beta with new api. So we found how gradle names this buidle variant (betaNewApi), created folder project/app/src/betaNewApi and put our class there, saving project structure for packages. As a result, classes that need this particular class take usual one or this particular only in this combination of flavors.
2) If you need to share not a whole class but only some small part of code, you can use runtime-checks for flavors:
if (BuildConfig.FLAVOR_releaseType.equals("prod"))
&& BuildConfig.FLAVOR_apiLvl.equals("newApi")) {
// here is your shared code
}
We extracted constants like "prod" into our Application class and use them in such ways.
I have an application which has 2 flavour dimensions. First dimension, lets call it "brand" has two types:
"strawberry", "cyan"
Both have different applicationIds, but we can focus only on one of those. Lets say "cyan" has applicationId "com.cyan.app".
The second flavour dimension is called say "environment". It has two values:
"prod", "test".
I also have two build types:
"debug", "release"
Now what I'm interested in, is how can I go about configuring the gradle script such that whenever I'm building debug versions there will be applicationIdSuffix which will contain both "debug" string and environment name string. So in the above example it would be:
com.cyan.app.debug.prod and com.cyan.app.debug.test
But if I build release version of an app I want to leave the main applicationId, so com.cyan.app, no matter the environment flavour.
Is there any way I can achieve that with gradle and android gradle plugin?
Ok, I've sit down during the weekend and was able to achieve that (and more). Sharing here so that others can benefit who would like to achieve the same thing as I posted in the question.
So first of all we have to attach ourselves to the applicationVariants.all task like so:
applicationVariants.all { variant ->
//We tweak the package name and application name depending on the variants (so that we can have multiple applications installed on the phone, depending on the
tweakPackageName(variant);
replaceInManifest(variant, 'android:label=\"#string/app_name\"', 'android:label=\"#string/
}
I've separated the logic to other methods so that the code is not clogged. The tweakPackageName method looks pretty simple (as it turned out):
/**
* Method that alters the package name in order to have many different variants of application installed simultanously on a device.
*/
ext.tweakPackageName = { variant ->
def envFlavor = getFlavorOfDimension(variant, flavorDimEnvironmentName);
variant.mergedFlavor.applicationId = variant.mergedFlavor.applicationId + ".${envFlavor.name.toLowerCase()}";
}
getFlavorOfDimension is a simple method to get the flavor of particular dimension I'm interested in (not a biggie so I won't spam with this code here). When we get the flavor we add it to the package name of the mergedFlavor object and we're done.
I also managed to not only change the package name but also to dynamically change the application launcher name, but there are plenty of solutions of that on StackOverflow so I won't be redundant here. All in all the solution to alter the package name works. Hopefully it will help someone like it helped me.