Best Practice to store link in string - android

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")
}
}
}
...

Related

How to configure extra properties to load in a android app initialization?

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.

What's the difference between buildConfigField, resValue and manifestPlaceholders?

I understand 'how' they are used, but want to know 'when' they are used.
For a case like using separate KEYs for different environments, we can use all three of the mentioned. So, I was curious to know why there are three things to do the same thing and if they are meant for different usages. Thanks!
Using buildConfigField will generate an actual Java constant in your app's generated BuildConfig class. So for your example, you would then have something like this:
public static class BuildConfig {
public static final String BASE_URL = "xxxxxxxxxx";
}
Using resValue will generate a resource of the type you specify into your app's res (resources) directory. So for a string, you'd be able to reference it via XML with #string/base_url or with getResources().getString(R.string.base_url).
Using manifestPlaceholders allows you to add a substitution in AndroidManifest.xml. So for example, GCM requires you to have a <uses-permission> tag with the name set to X.permission.C2D_MESSAGE, where X is your application ID. So if you have multiple build flavors with different application IDs, you can use a manifestPlaceholders tag and then use it in your AndroidManifest.xml where it will be replaced with the correct value, like:
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
For the record, applicationId is automatically added as a manifest placeholder, so there's no need to define it yourself, but that's just an example.
Basically, it depends on where you need it. If it's needed for an XML resource (a layout, menu, etc.), resValue is what you want. If it's needed to be accessed from Java code directly, buildConfigField will do the trick. For substitutions in the manifest, manifestPlaceholders is what you want.

Same code base, different APKs, differents ressources files and different webservices urls

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

ApplicationId conditioned by build type and flavour dimension

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.

Adding conditional statements in Android Manifest

I need a way to assign different Shared User ID(during installation) to the same Android App(same APK) based on the device model during installation.
I couldn't find any documentation about conditional statements being supported in the manifest file.
Any help would be greatly appreciated.
Though this is a pretty old question, but I stumbled upon a similar problem recently and this might help someone who's still searching for a solution. My case was that I had to assign some key in AndroidManifest.xml at runtime based on a condition.
While I couldn't find a way to define conditions in Manifest, I was able to pass the way to Manifest from build.gradle. If the condition can be checked in build.gradle, then you can pass value to Manifest in the following way
android {
defaultConfig {
manifestPlaceholders = [hostName:"www.example.com"]
}
...
}
And then access it in Manifest as -
<intent-filter ... >
<data android:scheme="https" android:host="${hostName}" ... />
...
</intent-filter>
Documentation - https://developer.android.com/studio/build/manifest-build-variables
Furthermore, you can generate strings, bool and other values from build.gradle which can then be used app-wide.
buildConfigField "int", "FOO", "42"
resValue "string", "app_name", "My App Name Debug"
The closest you can get to conditionality in the manifest is to not include a value as a constant, but rather use a redirect to the resources. You then provide different resources for different languages, devices etc.
The documentation for the manifest element states that the Shared User id should be a string, but for example the Shared User label must be a string resource (since you would typically need to translate it). Whether you can get away with making the id a resource as well is hard to guess - the documentation is often inaccurate about this, but be aware that what works on one version of Android may not be true for all versions.

Categories

Resources