2

I can access the TextView instance, get it's resource ID and get it's resource name via getResources().getResourceEntryName(), but I can't seem to find a way to get the id of the string associated with it.

How can I dynamically get the id of the string resource associated with a TextView ?

George Profenza
  • 50,687
  • 19
  • 144
  • 218

2 Answers2

1

You can't. A TextView doesn't store the id of the String resources if you call setText(int resid). All this overloaded method does is get the String from the resources and call the setText(CharSequence text) method.

Sander
  • 808
  • 6
  • 18
  • Hmmm, that's no fun. Wish there was a way – George Profenza Oct 31 '17 at 13:23
  • I don't know what your use case is but I'm sure you can keep track of what String resource you last set to the TextView in your own code. – Sander Oct 31 '17 at 13:56
  • I'm swapping some locale strings without changing the full os settings, just internal to my app. Is there an after `setContentView` is called on an activity ? – George Profenza Oct 31 '17 at 14:31
  • I'm not sure if I understand what you're trying to do. Are you trying to change the language in your app without changing the language of the entire phone? – Sander Oct 31 '17 at 14:46
  • That's right! So far I found [this](https://github.com/gunhansancar/ChangeLanguageExample), but the textviews and strings are hardcoded. I've started modifying the code and I can loop find TextView instances to swap text, but it would be great if I could make an activity superclass that did this as after onCreate, as soon as the extending activity called `setContentView` so the TextViews are present :) – George Profenza Oct 31 '17 at 14:51
  • So you're changing the text of the `TextViews` with different String resources (not languages) and you also want to let the user change the language, both during runtime? – Sander Oct 31 '17 at 15:07
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/157922/discussion-between-george-profenza-and-sander). – George Profenza Oct 31 '17 at 15:27
  • George Profenza! You very helped! Thank you! +100! :) – user_MGU Mar 25 '23 at 13:16
1

The only workaround I could was to match the +id of the TextView instances in the layout xml with the name of the string, then use Resources to get pair the two:

/**
     * finds a string id matching a text view id name and returns the value
     * @param textView - text view
     * @param context  - the context
     * @param locale   - the locale string
     * @return  String - resource string if found, empty string otherwise
     */
    public  static String getTextViewStringFromResources(TextView textView,Context context,String locale){
        String result = "";
        final int textViewID = textView.getId();

        if(textViewID <= 0){
            Log.e(TAG,"invalid id for textView " + textView + " text = " + textView.getText());
            result = textView.getText().toString();
            return result;
        }
        //get resources (re-usable)
        final Resources resources = getLocalizedResources(context,locale);
        // get textViews id
        final String entryName = resources.getResourceEntryName(textView.getId());
        // get the string id !! must match the textview id !!
        final int stringID = resources.getIdentifier(entryName,"string",context.getPackageName());
        //return string value for string id found by textview id
        try{
            result = resources.getString(stringID);
        }catch (Resources.NotFoundException e){
            Log.e(TAG,"couldn't find a string id for " + entryName);
            Log.e(TAG,e.getMessage());
        }
        return result;
    }

//!! requires minSDK 17
    @NonNull
    public static Resources getLocalizedResources(Context context, String locale) {
        Locale desiredLocale = new Locale(locale);
        Configuration conf = context.getResources().getConfiguration();
        conf.setLocale(desiredLocale);
        Context localizedContext = context.createConfigurationContext(conf);
        return localizedContext.getResources();
    }

The swapping is partially based on Gunhan Sancar's method of swapping text field.

This works but it pretty much a hack.

Eventually I needed to also change the Google Handwriting Input language programmatically and for that Juuso's ADB Change Language app came in super handy.

For reference, here's a function using Juuso's approach:

final String TAG = "ChangeLanguage"
//change language utils "kindly borrowed" from net.sanapeli.adbchangelanguage: brilliant little app!

    /**
     * updates a Configuration with a list of Locales
     * @param configuration - a configuration to updated
     * @param arrayList - an ArrayList of Locale instances
     * @return the updated configuration
     */
    @TargetApi(24)
    public static Configuration addLocalesToConfiguration(Configuration configuration, ArrayList<Locale> arrayList) {
        configuration.setLocales(new LocaleList((Locale[]) arrayList.toArray(new Locale[arrayList.size()])));
        return configuration;
    }

    /**
     * Change the system language
     * @param lanaguageList - a list of Locale instances to change to
     */
    public static void changeLanguages(ArrayList<Locale> lanaguageList){
        try {
            Class cls = Class.forName("android.app.ActivityManagerNative");
            Method method = cls.getMethod("getDefault", new Class[0]);
            method.setAccessible(true);
            Object invoke = method.invoke(cls, new Object[0]);
            method = cls.getMethod("getConfiguration", new Class[0]);
            method.setAccessible(true);
            Configuration configuration = (Configuration) method.invoke(invoke, new Object[0]);
            configuration.getClass().getField("userSetLocale").setBoolean(configuration, true);
            if (VERSION.SDK_INT >= 24) {
                configuration = addLocalesToConfiguration(configuration, lanaguageList);
            } else {
                configuration.locale = (Locale) lanaguageList.get(0);
            }
            Method method2 = cls.getMethod("updateConfiguration", new Class[]{Configuration.class});
            method2.setAccessible(true);
            method2.invoke(invoke, new Object[]{configuration});
            Log.d(TAG,"locale updated to" + lanaguageList.get(0).toString());
        } catch (Exception e) {
            Log.e(TAG,"error changing locale (double check you're granted permissions to the app first: pm grant your.app.package.here android.permission.CHANGE_CONFIGURATION )");
            e.printStackTrace();
        }
    }

Remember to grant CHANGE_CONFIGURATION to your app.

This is tested with Android 7.1.1 but bare in mind that it will fail on Android 8.0 (and potentially upwards) as getConfiguration is not found (the API is different) under certain build conditions.

The method expects an ArrayList of Locale objects with the language you want to change to on top: e.g.

ArrayList<Locale> fr = new ArrayList<Locale>();
fr.add(new Locale("fr"));
fr.add(new Locale("en"));

ArrayList<Locale> en = new ArrayList<Locale>();
en.add(new Locale("en"));
en.add(new Locale("fr"));

Make sure the Languages are installed on the devices as well.

If you have other improvement suggestions I'd be more than happy to update the answer

George Profenza
  • 50,687
  • 19
  • 144
  • 218
  • this is nonsense since it works only if textview has same res name as string res – Choletski Jun 03 '20 at 17:32
  • As I mentioned at the top: it's a hack (and not a pretty one, if there is such a thing) :) Totally agree with you on the res/string res. Also I don't like how it messes with the locale lists a user might have set in via OS settings. If you have something in mind it would be great if you could share a cleaner solution: please and thank you :D – George Profenza Jun 03 '20 at 20:13