Can I modify a Strings.xml file programmatically in Android? [duplicate] - android

I have declared a string in my strings.xml file , and using it in my activity as R.string.compose_title. (setting it as title i.e. setTitle(R.id.compose_title)). Now in some case I want to edit the string and then use it to set the title . How can I do this ?
P.S. I need to change value of a single string only , So declaring a new strings.xml for each case(which are variable depending upon the user) using localization seems to be a lil inefficient .

One thing what you have to understand here is that, when you provide a data as a Resource, it can't be modified during run time. For example, the drawables what you have in your drawable folder can't be modified at run time. To be precise, the "res" folder can't be modified programatically.
This applies to Strings.xml also, i.e "Values" folder. If at all you want a String which has to be modified at runtime, create a separate class and have your strings placed in this Class and access during run time. This is the best solution what I have found.

example howto:
how? by changing one variable reference to other reference
usage:
setRColor(pl.mylib.R.class,"endColor",pl.myapp.R.color.startColor);
// override app_name in lib R class
setRString(pl.mylib.R.class,"app_name",pl.myapp.R.string.app_name);
base methods:
public static void setRColor(Class rClass, String rFieldName, Object newValue) {
setR(rClass, "color", rFieldName, newValue);
}
public static void setRString(Class rClass, String rFieldName, Object newValue) {
setR(rClass, "string", rFieldName, newValue);
}
// AsciiStrings.STRING_DOLAR = "$";
public static void setR(Class rClass, String innerClassName, String rFieldName, Object newValue) {
setStatic(rClass.getName() + AsciiStrings.STRING_DOLAR + innerClassName, rFieldName, newValue);
}
helper methods :
public static boolean setStatic(String aClassName, String staticFieldName, Object toSet) {
try {
return setStatic(Class.forName(aClassName), staticFieldName, toSet);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return false;
}
}
public static boolean setStatic(Class<?> aClass, String staticFieldName, Object toSet) {
try {
Field declaredField = aClass.getDeclaredField(staticFieldName);
declaredField.setAccessible(true);
declaredField.set(null, toSet);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
#bradenV2 My app is supporting many languages , so I wanted to take a
string from my strings.xml that's currently in use and change that ,
and then use that one – atuljangra Mar 12 '12 at 22:04
ps the above solution is good for example when u want to inject some data in already compiled lib/jar. But if u want localize strings just make folder under res per LANG CODE like values-CC where cc is lang code (values-de,values-cs) etc
then u have 2 choices:
"build in" system dependent language selection - based on device selected lang
via create resources for configuration - you decide which lang show
like this:
configuration = new Configuration(resources.getConfiguration());
configuration.setLocale(targetLocale);
String localized = Context.createConfigurationContext(configuration)
.getResources()
.getString(resourceId);

I don't think you can programmatically customize the R class as it is built by ADT automatically.

I had a situation like this, where one of my strings.xml values had some dynamic piece of it. I set up the strings.xml with a "replacement text" (something like %%REPLACEMENT_EMAIL%%), and when I wanted to use that string programatically, I retrieved the string value of the resource, and replaced instances of that replacement text with the dynamic value (e.g. input by the user).
To be honest, my app has not been localized yet, but I'm still attempting to follow best practices w.r.t. not hardcoding any strings.

Use SharedPreferences instead of a Java class. It will give you more versatility if you decide to take values from the outside (web). Filling Java class in runtime can be useless offline. In case of SharedPreferences you have to ensure they are loaded only once, during app's first start, and then updated only by manual request, as previous import will be used.
myActivity.getSharedPreferences("com.example.imported",0)
.edit()
.putString("The news",getTheNews())
.apply();

Maybe you want to "modify" the string.xml so when it is required by the activity again it uses the new value, for example to keep a new dynamic title after screen rotation.
First, you can't modify the resource. It's already compiled. You can't modify the R class (what for?) all it's atributes are "final".
So, for the example above you can use onSaveInstanceState() and onRestoreInstanceState() for those properties you wanna keep on display.

According to my knowledge, you can't change resource value(R class value) while app running. why don't try to store on shared preference? I recommend you to use shared preference

I used below method to get the key-value pairs from the API and storing it in HashMap globally. If the key value is not found in HashMap then I will search that key in strings.xml file. It will achieve the purpose of dynamically changing the value of key.
public String getAppropriateLangText(String key) {
String value = "";
try {
HashMap<String, String> HashMapLanguageData HashMapLanguageData = gv.getHashMapLanguageData();
value = HashMapLanguageData.get(key);//Fetching the value of key from API
if (value == null || value.length() == 0) { //If Key value not found, search in strings.xml file
String packageName = getPackageName();
int resId = getResources().getIdentifier(key, "string", packageName);
value = getString(resId);
}
} catch (Exception e) {
value = "";
}
return value;
}

Related

Firestore - How to update a field that contains period(.) in it's key from Android?

Updating a field contains period (.) is not working as expected.
In docs, nested fields can be updated by providing dot-seperated filed path strings or by providing FieldPath objects.
So if I have a field and it's key is "com.example.android" how I can update this field (from Android)?
In my scenario I've to set the document if it's not exists otherwise update the document. So first set is creating filed contains periods like above and then trying update same field it's creating new field with nested fields because it contains periods.
db.collection(id).document(uid).update(pkg, score)
What you want to do is possible:
FieldPath field = FieldPath.of("com.example.android");
db.collection(collection).document(id).update(field, value);
This is happening because the . (dot) symbol is used as a separator between objects that exist within Cloud Firestore documents. That's why you have this behaviour. To solve this, please avoid using the . symbol inside the key of the object. So in order to solve this, you need to change the way you are setting that key. So please change the following key:
com.example.android
with
com_example_android
And you'll be able to update your property without any issue. This can be done in a very simple way, by encoding the key when you are adding data to the database. So please use the following method to encode the key:
private String encodeKey(String key) {
return key.replace(".", "_");
}
And this method, to decode the key:
private String decodeKey(String key) {
return key.replace("_", ".");
}
Edit:
Acording to your comment, if you have a key that looks like this:
com.social.game_1
This case can be solved in a very simple way, by encoding/decoding the key twice. First econde the _ to #, second encode . to _. When decoding, first decode _ to . and second, decode # to _. Let's take a very simple example:
String s = "com.social.game_1";
String s1 = encodeKeyOne(s);
String s2 = encodeKeyTwo(s1);
System.out.println(s2);
String s3 = decodeKeyOne(s2);
String s4 = decodeKeyTwo(s3);
System.out.println(s4);
Here are the corresponding methods:
private static String encodeKeyOne(String key) {
return key.replace("_", "#");
}
private static String encodeKeyTwo(String key) {
return key.replace(".", "_");
}
private static String decodeKeyOne(String key) {
return key.replace("_", ".");
}
private static String decodeKeyTwo(String key) {
return key.replace("#", "_");
}
The output will be:
com_social_game#1
com.social.game_1 //The exact same String as the initial one
But note, this is only an example, you can encode/decode this key according to the use-case of your app. This a very common practice when it comes to encoding/decoding strings.
Best way to overcome this behavior is to use the set method with a merge: true parameter.
Example:
db.collection(id).document(uid).set(new HashMap<>() {{
put(pkg, score);
}}, SetOptions.merge())
for the js version
firestore schema:
cars: {
toyota.rav4: $25k
}
js code
const price = '$25k'
const model = 'toyota.rav4'
const field = new firebase.firestore.FieldPath('cars', model)
return await firebase
.firestore()
.collection('teams')
.doc(teamId)
.update(field, price)
Key should not contains periods (.), since it's conflicting with nested fields. An ideal solution is don't make keys are dynamic, those can not be determined. Then you have full control over how the keys should be.

Set variable for R.drawable

At the moment I set a marker image for google maps on android like this:
.icon(BitmapDescriptorFactory.fromResource(R.drawable.mon1))
Where mon1 is the name of the corresponding to a image called mon1.png in drawable folder.
How can I do it like this:
String imagename="blablaimage";
.icon(BitmapDescriptorFactory.fromResource(R.drawable.imagename))
It is not possible to do what you suggested.
Instead, a possible workaround might be to use the following function:
public int getDrawableId(String name){
try {
Field fld = R.drawable.class.getField(name);
return fld.getInt(null);
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
and use like:
String imagename="blablaimage";
.icon(BitmapDescriptorFactory.fromResource(getDrawableId(imagename)));
If you click over R.drawable.mon1, you'll find an int declared with the name mon1 in R.java class. Everything resides in R class is basically an int. That said, you can't declare variable imagename as String to begin with. It must be int.
Everything generates in R.java class is auto generated by Android itself. Once you put some resources in appropriate directory, R.java generates corresponding resource int within so that it can be invoked from Java-end.
Bottom line, if you have an image called blablaimage somewhere in your drawable, you can (at best) do this,
int imagename= R.drawable.blablaimage;
.icon(BitmapDescriptorFactory.fromResource(imagename))
Another possibility using Resources.getIdentifier:
.icon(BitmapDescriptorFactory.fromResource(
context.getResources().getIdentifier(imagename, "drawable", "com.mypackage.myapp");
));

Android string resource from database

I'm searching for a workaround to replace string resource (strings.xml) whit the content of an SQLite table. The content of the table is changing, and synchronized whit an online database. So when I modify some text on the server, they should be changed also in the app, while running.
I don't want to set every single textview's and other texts pragmatically, or create custom textview etc.
Is it possible to generate xml from that database table and use it as string resource? (In run time of course and the generated xml would be exactly the same as the original, except of the string values, so the keys wont changes)
Or does anybody have an idea to solve this problem?
There is no way to change the strings.xml file on the fly. What you can do is to abstract string gathering.
class YourUtilites {
static HashMap<String, String> dbCache = new HashMap<String, String>();
public static String getString(Context context, String key, String defaultID) {
String value = null;
value = dbCache.get(key);
if (value == null) {
// Check if the key exists in the database
String value = db.somedbFunctionToGetString(key);
if (value != null) {
dbCache.put(key, value);
}
}
if (value == null) {
// If value is not in the database, resort to the default value
value = context.getResources().getString(defaultID);
}
return value;
}
}
Then you can use it in the textviews like this
textView.setText(YourUtilities.getString("dbKey", R.id.defaultValue1, this);
textView2.setText(YourUtilities.getString("dbKey2", R.id.defaultValue2, this);
...
This way you can update your db values and have it show up immediately. Don't forget to clear the dbCache when you update the database values. This solution obvious goes without saying that db accesses are slow so keep that in mind.
Hope this helps.

How to easily iterate over all strings within the "strings.xml" resource file?

I created an application that uses the TTS engine to send feedback to the user. With the aim to improve the performance, I used the synthesizeToFile and addSpeech methods, but strings of text to be synthesized are inside the strings.xml file, so I have to invoke these methods for each string that is spoken by the TTS engine.
Since the TTS engine uses only strings whose name begins with tts_, is it possible to easily iterate over all strings that begin with tts_ within the strings.xml file?
You can get all the strings in strings.xml via reflection, and filter out only the ones you need, like so:
for (Field field : R.string.class.getDeclaredFields())
{
if (Modifier.isStatic(field.getModifiers()) && !Modifier.isPrivate(field.getModifiers()) && field.getType().equals(int.class))
{
try
{
if (field.getName().startsWith("tts_"))
{
int id = field.getInt(null);
// do something here...
}
} catch (IllegalArgumentException e)
{
// ignore
} catch (IllegalAccessException e)
{
// ignore
}
}
}
You can give them all (while defining) the resource name as "prefix"+(1..n). And in the code use,
int resid=<constant>;
for(i=1;resid!=0;i++){
resid = this.getResources().getIdentifier("prefix"+i, "strings", this.getPackageName());
}
You could put these TTS strings into a TypedArray.
you can use this code:
String[] strings = getResources().getAssets().list("string");
for (int i = 0; i < strings.length; i++) {
Log.d("aaa ", strings[i]);
}
to iterate through other resources like fonts,... just replace string with folder name.
In all my projects, i just observed that the value of strings in R.java starts with 0x7f050000 and it counts upwards, like 0x7f050001, 0x7f050002, 0x7f050003,....
You could just ++ them :D
Hope it helps :)

Default value on Bundle.getString(String key)

I've just noticed that, while most of getters from a Bundle have the possibiliy of including a default value, in case the key doesn't exist in that particular bundle instance, getString does not have that possibility, returning null if that case.
Any ideas on why is that and if there is some way of easy solution to that (by easy I mean not having to check each individual value or extending the Bundle class).
As an example, right now you have only this:
bundle.getString("ITEM_TITLE");
While I would like to do:
bundle.getString("ITEM_TITLE","Unknown Title");
Thanks!
Trojanfoe has the best solution if that is what you want, though once you get into dealing with defaults for other datatypes you'll have to do the same thing for them all.
Another solution would be to check to see if the bundle contains the key:
String myString = bundle.containsKey("key") ? bundle.getString("key") : "default";
It's not as nice as a function, but you could always wrap it if you wanted.
You'll have to wrap it yourself:
public String getBundleString(Bundle b, String key, String def)
{
String value = b.getString(key);
if (value == null)
value = def;
return value;
}
Note: http://developer.android.com/reference/android/os/Bundle.html
public String getString (String key, String defaultValue)
Since: API Level 12
EDIT this function has moved to BaseBundle: here
Another solution is to check for null:
String s = bundle.getString("key");
if (s == null) s = "default";
This is better than csaunders' solution because the Bundle can contain the respective key but it can be of a different type (for example an int), in which case his solution would result myString in being null, instead of "default".

Categories

Resources