I want to change the language of the app and this works fine until API 26.
For api > 25 I put Locale.setDefault(Locale.Category.DISPLAY, mynewlanglocale); before setContentView(R.layout.activity_main); but nothing changes.
The docs don't explain too much about this.
I had the same problem: since Android 8.0+ some parts of my app did't change their language anymore. Updating of both application and activity context helps me. Here is an example of MainActivity function:
private void setApplicationLanguage(String newLanguage) {
Resources activityRes = getResources();
Configuration activityConf = activityRes.getConfiguration();
Locale newLocale = new Locale(newLanguage);
activityConf.setLocale(newLocale);
activityRes.updateConfiguration(activityConf, activityRes.getDisplayMetrics());
Resources applicationRes = getApplicationContext().getResources();
Configuration applicationConf = applicationRes.getConfiguration();
applicationConf.setLocale(newLocale);
applicationRes.updateConfiguration(applicationConf,
applicationRes.getDisplayMetrics());
}
Yes in android Oreo localization is not working fine with updateconfiguration. But it is deprecated in android N itself. Instead of updateconfiguration use createconfiguration in each attachcontext. it is working fine for me. Try this...
In you activity add this..
#Override
protected void attachBaseContext(Context newBase) {
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
super.attachBaseContext(MyContextWrapper.wrap(newBase, "ta"));
}
else {
super.attachBaseContext(newBase);
}
}
In MyContextWrapper.java
public static ContextWrapper wrap(Context context, String language) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
Locale newLocale = new Locale(language);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}
updateConfiguration is deprecated and you should use createConfigurationContext. I solved it this way:
#Override
protected void attachBaseContext(Context newBase) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Configuration config = newBase.getResources().getConfiguration();
//Update your config with the Locale i. e. saved in SharedPreferences
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(newBase);
String language = prefs.getString(SP_KEY_LANGUAGE, "en_US");
Locale.setDefault(locale);
config.setLocale(new Locale(language));
newBase = newBase.createConfigurationContext(config);
}
super.attachBaseContext(newBase);
}
Updated For All android versions till Oreo
Create a class like this
public class LocaleUtils {
#Retention(RetentionPolicy.SOURCE)
#StringDef({ENGLISH, FRENCH, SPANISH})
public #interface LocaleDef {
String[] SUPPORTED_LOCALES = {ENGLISH, FRENCH, SPANISH};
}
public static final String ENGLISH = "en";
public static final String FRENCH = "fr";
public static final String SPANISH = "es";
public static void initialize(Context context) {
setLocale(context, ENGLISH);
}
public static void initialize(Context context, #LocaleDef String defaultLanguage) {
setLocale(context, defaultLanguage);
}
public static boolean setLocale(Context context, #LocaleDef String language) {
return updateResources(context, language);
}
private static boolean updateResources(Context context, String language) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
context.createConfigurationContext(configuration);
configuration.locale = locale;
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return true;
}
}
Now when you select the language from your app, Save the language code in Shared Preference like below
private static SharedPreferences getDefaultSharedPreference(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(Application.getInstance().getApplicationContext()) != null)
return PreferenceManager.getDefaultSharedPreferences(Application.getInstance().getApplicationContext());
else
return null;
}
public static void setSelectedLanguageId(String id){
final SharedPreferences prefs = getDefaultSharedPreference(Application.getInstance().getApplicationContext());
SharedPreferences.Editor editor = prefs.edit();
editor.putString("app_language_id", id);
editor.apply();
}
public static String getSelectedLanguageId(){
return getDefaultSharedPreference(Application.getInstance().getApplicationContext())
.getString("app_language_id", "en");
}
These three functions should be written inside a Utiltiy class(your preference). Then when you select the app language from the app, call the setSelectedLanguageId() function and pass the language id as parameter.
This way you have saved the selected language in your app. Now in your application class write a function like this
public void initAppLanguage(Context context){
LocaleUtils.initialize(context, PreferenceUtil.getSelectedLanguageId() );
}
Here the PreferenceUtil is my Utiltiy class. You should replace it with your utility class function.
You should also create a variable in your application class
private static Application applicationInstance;
and in your Application class's onCreate method, initialise applicationInstance to be the applications context like this
applicationInstance = this;
Now write a getter function in your application class
public static synchronized Application getInstance() {
return applicationInstance;
}
And now when you start your first activity, call this method in your activity's onCreate
Application.getInstance().initAppLanguage(this);
Remember that we are passing the activity's context to the initAppLanguage() function, not the application context. Passing the Application context won't make it work in Oreo(atleast for me).
So when you select the language try to restart your application completely.
You can acheive this by
Intent i = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
startActivity(i);
Hope this helps you!
It is possible, however i would not recommend to set the language programatically
Android is designed so the System UI and your App have the same language, if you change it programmatically you would be fighting the system
Instead what you can do is enable multilanguage support by adding different strings.xml languages, this will change the language automatically
I reccommend reading through this Google Developers article:
Supporting Different Languages and Cultures
If you really need to change it programatically you can do the following
Locale locale = new Locale("en");
Locale.setDefault(locale);
Configuration config = context.getResources().getConfiguration();
config.setLocale(locale);
context.createConfigurationContext(config);
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
On SDK >= 21, you need to call 'Resources.updateConfiguration()', otherwise resources will not be updated.
Hope it helps.
Here is complete solution worked for kitkat, Lollipop, Marshmallow, Nougat and Oreo too. Just follow all below step.
First create a java class like below
import android.content.Context;
import android.content.res.Configuration;
import java.util.Locale;
public class LocaleUtils {
public static void updateConfig(Context mContext, String sLocale) {
Locale locale = new Locale(sLocale);
Locale.setDefault(locale);
Configuration config = mContext.getResources().getConfiguration();
config.locale = locale;
mContext.getResources().updateConfiguration(config,
mContext.getResources().getDisplayMetrics());
}
}
Now add this snippet on Button click where you want to change locale
String lang="hi";//pass your language here
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences.Editor editor = preferences.edit();
editor.clear();
editor.putString("lang", lang");
editor.putBoolean("langSelected", true);
editor.apply();
LocaleUtils.updateConfig(mContext,lang);
Intent intent = mContext.getIntent();
mContext.overridePendingTransition(0, 0);
mContext.finish();
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mContext.overridePendingTransition(0, 0);
mContext.startActivity(intent);
Finally paste the below code in Splash Activity or in Launching Activity.
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
String lang = preferences.getString("lang", "");
boolean langSelected = preferences.getBoolean("langSelected", false);
SharedPreferences.Editor editor = preferences.edit();
if (langSelected) {
editor.clear();
editor.putString("lang", lang);
editor.putBoolean("langSelected", true);
editor.apply();
LocaleUtils.updateConfig(this,lang);
} else {
LocaleUtils.updateConfig(this, Locale.getDefault().getLanguage());
editor.clear();
editor.putString("lang", Locale.getDefault().getLanguage());
editor.putBoolean("langSelected", false);
editor.apply();
}
After use all solution in all sources finally i found my issue. That makes me angry for 2 days.
Everyone knows that in Android Oreo (API 26) we must use createConfigurationContext, But My problem is using Country name with local.
Replace
en_US with en
ar_AE with ar
fa_IR with fa
And my problem solved.
Hope to help someone
You need to use getApplicationContext() instead of getContext()
Related
First time using Stackoverflow !
I have an issue with my Workmanager and I'm asking for help:
When I run my app, its executing without switching my switch to ON. It's happening every time when I install (run) my app, my notification appears without doing anything. (it still works when I'm using my switch after launching )
MyWorker.java
public class MyWorker extends Worker {
private Workmate workmate;
private String messageBody;
public MyWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
retrievesWorkmateData();
return Result.success();
}
SettingsActivity.java
#Override
public int getLayout() {
return R.layout.activity_settings;
}
#Override
protected void onConfigureDesign() {
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
configureToolbar();
spinnerLanguage();
final SharedPreferences.Editor editor = mSharedPreferences.edit();
boolean notificationBoolean = mSharedPreferences.getBoolean(BOOLEAN, false);
final OneTimeWorkRequest simpleRequest = new OneTimeWorkRequest.Builder(MyWorker.class)
.build();
UUID workId = simpleRequest.getId();
if (notificationBoolean) {
mSwitch.setChecked(true);
}
mSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (mSwitch.isChecked()) {
editor.putBoolean(BOOLEAN, true);
WorkManager.getInstance().enqueue(simpleRequest);
Toast.makeText(this, getResources().getString(R.string.Alarm_manager_start), Toast.LENGTH_SHORT).show();
} else {
editor.putBoolean(BOOLEAN, false);
WorkManager.getInstance().cancelWorkById(workId);
Toast.makeText(this, getResources().getString(R.string.Alarm_manager_cancel), Toast.LENGTH_SHORT).show();
}
editor.apply();
});
}
Have a nice day.
inside onConfigureDesign you are creating OneTimeWorkRequest an obtain its UUID workId = simpleRequest.getId(). this id is used inside OnCheckedChangeListener for starting or canceling work for WorkManager. consider this scenario: app doesn't have any pending work to do and user navigates to this switch and schedule work with given UUID. then user quits app, even kill it, and then got back to app and navigates again to this switch. onConfigureDesign is called again, new OneTimeWorkRequest is created and it have new UUID. this id won't cancel your previously set request, because it had/have another id... still switch is checked, as its checked/unchecked state is basing just on some boolean in SharedPreferences
solution would be to store in SharedPreferences this UUID (String in fact, use toString() and fromString(str)) when work is scheduled and remove it from there when canceling or doWork() gets called. initial state of switch should be also set basing on presence of this (any) id in shared prefs
welcome on SO :)
I'm having trouble with changing the app language manually, in the app, I offer users the ability to change the app's language to their preferred, the code below works fine even in Android (Pixel 3 Emulator), but for some reason, it doesn't work on all Samsung devices
Context context = LocaleUtils.setLocale(getApplicationContext(), languageCode);
Resources resources = context.getResources();
Locale myLocale = new Locale(languageCode);
DisplayMetrics dm = resources.getDisplayMetrics();
Configuration conf = resources.getConfiguration();
conf.locale = myLocale;
resources.updateConfiguration(conf, dm);
Intent intent = getBaseContext().getPackageManager().getLaunchIntentForPackage(
getBaseContext().getPackageName());
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
Application class:
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
LocaleUtils.onAttach(base, Locale.getDefault().getLanguage());
MultiDex.install(this);
}
on each Activity:
#Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(ViewPumpContextWrapper.wrap(LocaleUtils.onAttach(newBase)));
}
I struggled with dynamic Locale change on Samsung devices even prior to Android 10.
There might be a better solution now,
but at the time in addition to what you have done,
I ended up retrieving all the strings by resource identifiers in the following way:
public static String getStringByIdentifier(final Context context, final String stringResIdName, final boolean forceRefresh) {
final Resources res = getResources(context, forceRefresh);
String result;
try {
result = res.getString(res.getIdentifier(stringResIdName, "string",
context.getPackageName()));
} catch (final Resources.NotFoundException e) {
result = stringResIdName; // TODO or here you may throw an Exception and handle it accordingly.
}
return result;
}
public static String getStringByIdentifier(final Context context, final String stringResIdName) {
return getStringByIdentifier(context, stringResIdName, false);
}
private static Resources getResources(final Context context, final boolean refreshLocale) {
if (!refreshLocale) {
return context.getResources();
} else {
final Configuration configuration = new Configuration(context.getResources().getConfiguration());
configuration.setLocale(Locale.getDefault());
return context.createConfigurationContext(configuration).getResources();
}
}
So you have to set text in the next way:
textView.setText(AndroidUtils.getStringByIdentifier(context, "string_res_name"));
Where the corresponding string resource is:
<string name="string_res_name">Some string</string>
I have been wondering for a while why my onCreate method is run twice and have now found out that it has to do with me setting the locale of the app at launch... My question is, is it necessary for it to run twice or not?
This is the code that makes onCreate run twice:
/*Sets the language of the application and also returns the integer value of selected language*/
protected Integer setLanguage() {
String lang = prefs.getString("language-key","0");
Integer language = Integer.parseInt(lang);
Configuration config = context.getResources().getConfiguration();
if (!decideLang(language).equals("") && !config.locale.getLanguage().equals(decideLang(language))) {
setLocale(decideLang(language));
}
return language;
}
/*Sets the locale*/
private void setLocale(String lang) {
((Activity) context).recreate();
Locale myLocale = new Locale(lang);
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
Configuration conf = res.getConfiguration();
conf.locale = myLocale;
res.updateConfiguration(conf, dm);
}
The integer that the setLanguage method returns is later used to determine what URL to use in a later stage but I have come to realize that is not important for my question.
My question is, WHY does onCreate need to run twice because of this code?
((Activity) context).recreate();, as it states on the tin, recreates the Activity, so onCreate() is, of course, going to be called twice.
(From comments)
I made a method I'm using in onCreate, it's called loadSounds, and depending on the version I use the SoundPool Builder or the deprecated version if it's an older version of Android......But sounds only work on Lollipop devices......What could be wrong....the app runs in both versions.....The variable that holds the SoundPool is declared outside of onCreate as an object attribute private static SoundPool mySounds;
Thanks in advance!!!
private void loadSounds(){
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN).setUsage(AudioAttributes.USAGE_GAME).build();
mySounds = new SoundPool.Builder().
setMaxStreams(10).
setAudioAttributes(audioAttributes).
build();
correctAnswerID = mySounds.load(this, R.raw.correctanswer, 1);
incorrectAnswerID = mySounds.load(this, R.raw.incorrectanswer, 1);
}
else
{
mySounds= new SoundPool(10, AudioManager.USE_DEFAULT_STREAM_TYPE,1);
correctAnswerID = mySounds.load(this, R.raw.correctanswer, 1);
incorrectAnswerID = mySounds.load(this, R.raw.incorrectanswer, 1);
}
}
The method that uses the sounds is(if the answer is correct plays the correctanswer mp3, otherwise it plays the wronganswer mp3) :
if(selectedAnswer.equals(currentAnswer) || isEnharmonic ){
correctAnswers+=1;
reproduceAnswerSound("Correct");
}
else
{
incorrectAnswers+=1;
reproduceAnswerSound("Incorrect");
}
and the method that reproduce the sounds is like this:
private void reproduceAnswerSound(String type){
if(type.equals("Correct")){
mySounds.play(correctAnswerID,1,1,1,0,1);
}else if(type.equals("Incorrect"))
{
mySounds.play(incorrectAnswerID,1,1,1,0,1);
}
}
I need some help with debugging my application. First of all: In emulator and on some other devices my app is running fine. On my device I got a force close (without a force close message).
The "crash" happens if the Activity of the app is changed.
Here is some code of the MainActivity class. It just reads html content from a web page over webview. And no, it is NOT possible to do this over HttpRequest because I was not able to simulate the post request.
public class MainActivity extends Activity {
public final static String EXTRA_HTML = "com.example.com.test.HTML";
private WebView mWebView;
private ProgressDialog mDialog;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webView1);
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
mWebView.setBackgroundColor(0);
mWebView.setWebChromeClient(new WebChromeClient() {
public boolean onConsoleMessage(ConsoleMessage cmsg) {
if (cmsg.message().startsWith("MAGIC")) {
mDialog.cancel();
/*HashMap<String, String> message = new HashMap<String, String>();*/
String msg = cmsg.message().substring(5);
Intent intent = new Intent(MainActivity.this,
ReadDataActivity.class);
/*message.put("message", msg);*/
/*intent.putExtra(EXTRA_HTML, message);*/
intent.putExtra(EXTRA_HTML, msg);
startActivity(intent);
}
return false;
}
});
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setPluginState(PluginState.OFF);
mWebView.getSettings().setLoadsImagesAutomatically(false);
mWebView.getSettings().setBlockNetworkImage(true);
mWebView.getSettings().setAppCacheEnabled(true);
mWebView.getSettings().setSavePassword(true);
mWebView.getSettings()
.setCacheMode(WebSettings.LOAD_NORMAL);
mWebView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String address) {
if (address.indexOf("mySession") != -1) {
view.loadUrl("javascript:console.log('MAGIC'+document.getElementsByTagName('html')[0].innerHTML);");
}
});
mWebView.loadUrl("http://www.myurl.de");
}
So, in the onConsoleMessage() method I just pass the html code to another Activity class which read, parse and display the content.
The problem is now that at this point when the ReadDataActivity class should be loaded the application just close and go back to the home screen without any message or user dialog.
Is it possible that the html code which is passed as a string to the ReadDataActivity is to big? I also try to add the html code as a string in a HashMap but the problem is the same.
Some ideas what I can do to debug the problem? Maybe I should try to create a Parcelable object?
In the emulator everything is working fine.
As per my experience (sometime ago), you are able to parcel up to 1MB of data in a Bundle for IPC. This limit can be reduced if a lot of transactions are happening at a given time. Further information here.
In order to overcome this issue, I would suggest you to save your content on a temp file and pass the path/URI of your temp file to your second activity. Then in your second activity, read the contents out from file, perform your desired operations and finally delete that file.
If you want, you may also incorporate Shared_Preferences for this task - if you think handling files is cumbersome.
I did some research on the maximum amount of data you can transfer using an Intent. And it seems that the limit is nowhere near 1MB or 90KB, it's more like 500KB (tested on API 10, 16, 19 and 23).
I wrote a blog post about this topic, you can find it here: http://web.archive.org/web/20200217153215/http://neotechsoftware.com/blog/android-intent-size-limit
The size limit of Intent is still pretty low in Jelly Bean, which is somewhat lower than 1MB (around 90K), so you should always be cautious about your data length, even if your application targets only latest Android versions.
I have seen that by writing and reading from a file consists of less performance .
Then I have seen this solution : . So I am using this solution :
public class ExtendedDataHolder {
private static ExtendedDataHolder ourInstance = new ExtendedDataHolder();
private final Map<String, Object> extras = new HashMap<>();
private ExtendedDataHolder() {
}
public static ExtendedDataHolder getInstance() {
return ourInstance;
}
public void putExtra(String name, Object object) {
extras.put(name, object);
}
public Object getExtra(String name) {
return extras.get(name);
}
public boolean hasExtra(String name) {
return extras.containsKey(name);
}
public void clear() {
extras.clear();
}
}
Then in MainActivity I have called it like the following :
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
extras.putExtra("extra", new byte[1024 * 1024]);
extras.putExtra("other", "hello world");
startActivity(new Intent(MainActivity.this, DetailActivity.class));
and in DetailActivity
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
if (extras.hasExtra("other")) {
String other = (String) extras.getExtra("other");
}
The fixed size of 1MB is not only limited to intents. As Intents, Content Providers, Messenger, all system services like Telephone, Vibrator etc. utilize IPC infrastructure provider by Binder. Moreover the activity lifecycle callbacks also use this infrastructure.
1MB is the overall limit on all the binder transactions executed in the system at a particular moment.
In case there are lot of transactions happening when the intent is sent,it may fail even though extra data is not large.
http://codetheory.in/an-overview-of-android-binder-framework/
A little late to the game, but I just ran up against the same issue. Writing the data to file didn't really make sense performance-wise in my case, but I came across this in my search for answers:
http://developer.android.com/guide/faq/framework.html#3
Using a singleton is better for me as there's no need for disk IO. Better performance if the data doesn't need to be persisted.
Here's an example implementation:
public class DataResult {
private static DataResult instance;
private List<SomeObject> data = null;
protected DataResult() {
}
public static DataResult getInstance() {
if (instance == null) {
instance = new DataResult();
}
return instance;
}
public List<SomeObject> getData() { return data; }
public void setData(List<SomeObject> data) { this.data = data; }
}
Then you can set using this in one activity:
DataResult.getInstance().setData(data);
And get it in the other activity like this:
List<SomeObject> data = DataResult.getInstance().getData();
The Binder transaction buffer has a limited fixed size - 1Mb.
But the problem is that buffer shared by all transactions in progress for the process.
So try to keep your intent's data as small as possible every time.
The use of static String variable is good. If there is a need for the user to go back & forth between different pieces of HTML, you can also use LruCache like this:
static LruCache<String, String> mMemoryCache;
final int kiloByte = 1024;
.
.
final int maxMemoryKB = (int) (Runtime.getRuntime().maxMemory() / kiloByte);
// then choose how much you want to allocate for cache
final int cacheSizeKB = maxMemoryKB / 8;
.
.
mMemoryCache = new LruCache<String, String>(cacheSizeKB) {
//#Override
protected int sizeOf(String key, String value) {
try {
byte[] bytesUtf8 = value.getBytes("UTF-8");
return bytesUtf8.length / kiloByte;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return -1;
}
};
.
.
String cacheKey = generateUniqueString(key);
if (mMemoryCache.get(key) == null) {
mMemoryCache.put(cacheKey, yourContent);
}
Intent intent = new Intent(getApplicationContext(), ReadDataActivity.class);
intent.putExtra(EXTRA_HTML, cacheKey);
startActivity(intent);
Then on the ReadDataActivity side
Intent intent = getIntent();
String cacheKey = intent.getStringExtra(EXTRA_HTML);
String contentString = MainActivity.mMemoryCache.get(cacheKey);
doSomethingWith(contentString);
This idea came from here.
An alternative solution for passing large data between activities is to use a static field. In your case add this line to ReadDataActivity class
public static String msg;
Then you can use the static field msg within MainActivity class as follows
ReadDataActivity.msg = cmsg.message().substring(5);
And finally start your activity without extra put
Intent intent = new Intent(MainActivity.this, ReadDataActivity.class);
startActivity(intent);