I have an in-app language switcher in a Kotlin app (two buttons, one button for English, one button for Romanian).
It does not work on devices with Android 12 (API level 32) and lower. On the newer ones, it works perfectly.
I'm following the official guide here. I have a class LocaleHelper, that has this method:
fun setLanguage(language: String) {
App.preferences.edit().putString(App.LANGUAGE_SELECTION, language).apply()
val tag = "$language-RO"
val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(tag)
AppCompatDelegate.setApplicationLocales(appLocale)
}
which I'm calling when a language button is clicked:
when (item?.itemId) {
R.id.romanian_button -> {
LocaleHelper().setLanguage("ro")
}
R.id.english_button_button -> {
LocaleHelper().setLanguage("en")
}
}
The Romanian language IS available on users devices, because in the MainActivity.kt, I'm already downloading the additional language (Romanian) according to the official Google code sample from github.
Also, in AndroidManifest.xml I've put this, according to the official guide for older devices:
<application>
...
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="true"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
...
</application>
The problem is that on devices with Android 12 (API level 32) and lower, this does nothing. When I tap the flags, nothing happens, the language remains English, even if the activity is recreating itself. On newer devices, this code works. What did I do wrong?
By using Android's new Android App Bundle, I have received a Resource Not Found error in 2 of my Google Play Store apps.
Here is the stacktrace from fabric for one of the apps:
Unable to start activity ComponentInfo{/com.Lastyear.MainActivity}: android.content.res.Resources$NotFoundException: File res/drawable/abc_item_background_holo_dark.xml from drawable resource ID #0x7f08002c
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2377)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2429)
at android.app.ActivityThread.access$800(ActivityThread.java:151)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1342)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:5363)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:828)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:644)
at dalvik.system.NativeStart.main(NativeStart.java)
build.gradle dependencies:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support:customtabs:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.android.support:palette-v7:27.1.1'
implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
implementation 'com.jakewharton:butterknife:8.8.1'
implementation 'com.github.bumptech.glide:glide:3.7.0'
implementation 'com.android.support:design:27.1.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
implementation 'com.github.hotchemi:android-rate:1.0.1'
implementation 'com.hannesdorfmann.smoothprogressbar:library:1.0.0'
implementation 'com.android.support:palette-v7:27.1.1'
implementation 'com.google.android.gms:play-services-ads:15.0.1'
implementation 'com.muddzdev:styleabletoast:1.0.9'
implementation 'com.github.GrenderG:Toasty:1.2.5'
implementation 'com.hannesdorfmann.smoothprogressbar:library:1.0.0'
implementation 'com.wang.avi:library:2.1.3'
implementation 'com.github.medyo:fancybuttons:1.8.4'
implementation 'com.irozon.sneaker:sneaker:1.0.1'
implementation 'com.sdsmdg.tastytoast:tastytoast:0.1.1'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.github.barteksc:android-pdf-viewer:2.8.2'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.11.0'
implementation('com.crashlytics.sdk.android:crashlytics:2.6.8#aar') {
transitive = true;
}
implementation 'petrov.kristiyan:colorpicker-library:1.1.8'}
One more thing, it is happening only on Android 4 operating system, not on newer versions of Android. I have found that other apps are having the same problem of Resource Not Found, which was not existing before using Android's app bundle. Is there some problem in the library or code or it is because of the beta version of Android's app bundle?
I have also found the resource drawable due to which it crashes:-
I think this question is also related to this one: Resource Not Found error res/drawable/abc_switch_thumb_material.xml after adding SwitchCompat in Android App Bundle
This is almost certainly users sharing (sideloading) the app, either via P2P sharing programs, or uploading the APK to the web then other users downloading and installing from the web.
People used to dealing with non Android App Bundle apps just transfer and share the main APK. But your App bundle app has lots of "split APKs" for things like the resources, that is how the size saving happens. You can read all about this process on the help page. If a user installs the main APK without installing the right split APKs, then a "Resources Not found" crash will occur the first time the app tries to load a resource.
If you want to support users sideloading your app and just the main APK you could try to detect this situation and display a message to the user (without using any resources) that says "Please install from Google Play". Or you could just decide you aren't going to support users who share APKs in this way.
I suspect in the long run the websites and P2P sharing programs will get better at sharing such APKs properly, so I wouldn't spend too long worrying about it.
If you see this happening far more frequently on lower Android versions, this isn't probably due to a bug in lower Android versions. Instead, it is probably because in countries where users commonly P2P share apps (eg India) users also are far more likely to be on older version phones.
This is a little late but Google has introduced new API for Sideloading crash prevention, which allows you to detect incomplete installation of apps that are built using an Android App Bundle.
For example, consider an app that uses Android App Bundles to optimize
app download size using split APKs. When a user downloads the app from
the Google Play store, it ensures that the device downloads and
installs the complete set of split APKs required to run that app on
that particular device. When you bypass Google Play to sideload an
app, the platform does not have sufficient data to validate the app
install, and proper functionality of the app is not guaranteed.
First off include the Play Core library 1.6.0 or higher in your project.
Include the following in your app project’s build.gradle file:
buildscript {
dependencies {
...
// Use bundletool 0.9.0 or higher when building with the
// Android Gradle plugin.
classpath 'com.android.tools.build:bundletool:0.9.0'
}
}
You can use 1 of those 3 below methods
1) Register checks through the manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication" >
<application
...
android:name="com.google.android.play.core.missingsplits.MissingSplitsDetectingApplication" >
</application>
...
</manifest>
2) Apply checks in a custom Application class
public class MyCustomApplication extends Application {
#Override
public void onCreate() {
if (MissingSplitsManagerFactory.create(this).disableAppIfMissingRequiredSplits()) {
// Skip app initialization.
return;
}
super.onCreate();
...
}
}
3) Apply checks to content providers
public class ExampleProvider extends ContentProvider {
#Override
public boolean onCreate() {
if (MissingSplitsManagerFactory.create(getContext()).isMissingRequiredSplits()) {
// Skip provider initialization.
return false;
}
super.onCreate();
...
}
}
Read More : https://developer.android.com/reference/com/google/android/play/core/release-notes?hl=en-419#1-6-0
The accepted answer is absolutely correct - root of this issue is sideloading of APK file.
Nevertheless, lot of people are still looking for workaround, asking how to correctly handle this case.
In my app I did the following:
Create 1x1 image named pixel.png and put it to all of the following folders: drawable-mdpi, drawable-hdpi, drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi.
Create simple Activity which shows static message, e.g.
This copy of app is corrupted and can't be launched.
Please, install original version from Google Play
Then simply call getDrawable(R.drawable.pixel) from Activity.onCreate() wrapped in try/catch clause.
If the exception was caught, just finish current Activity and start another one from step #2.
Done!
This solution works well, now I even have data from Firebase Analytics confirming this.
From the 46k of new users (event first_open) 266 users got this error (which was caught) and 221 users clicked button which leads to Google Play.
Here is my source code (also available on GitHub):
DrawablesValidator.java
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Space;
import android.widget.TextView;
import android.widget.Toast;
public class DrawablesValidator extends Activity {
public static void ensureDrawablesValid(#NonNull Activity activity) {
try {
// IMPORTANT create 1x1 image named pixel.png and put it to all folders
// drawable-mdpi
// drawable-hdpi
// drawable-xhdpi
// drawable-xxhdpi
// drawable-xxxhdpi
activity.getDrawable(R.drawable.pixel);
} catch (Resources.NotFoundException ex) {
// NOTE optionally, report exception to Crashlytics or just an event to Analytics
activity.finish();
activity.startActivity(new Intent(activity, DrawablesValidator.class));
}
}
// NOTE don't care about translations of text messages here, don't put them to strings.xml
// we assume, that if user is smart enough to get APK from outside and install it,
// then user will definitely understand few messages in English :)
#SuppressLint("SetTextI18n")
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
int dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
int dp8 = dp * 8;
int dp16 = dp * 16;
int dp80 = dp * 80;
LinearLayout root = new LinearLayout(this);
root.setOrientation(LinearLayout.VERTICAL);
root.setGravity(Gravity.CENTER_HORIZONTAL);
root.setPadding(dp80, dp16, dp80, dp16);
Space spaceTop = new Space(this);
TextView title = new TextView(this);
title.setPadding(0, dp8, 0, dp8);
title.setTextSize(20);
title.setText("Re-install app");
TextView message = new TextView(this);
message.setPadding(0, dp8, 0, dp8);
message.setTextSize(16);
message.setText("This copy of app is corrupted and can't be launched." +
"\n\n" +
"Please, install original version from Google Play");
Button button = new Button(this);
button.setPadding(dp16, dp8, dp16, dp8);
button.setText("Continue");
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + getPackageName())));
} catch (Exception ex) {
Toast.makeText(getApplicationContext(), "Can't open Google Play", Toast.LENGTH_SHORT).show();
}
}
});
Space spaceBottom = new Space(this);
int wc = ViewGroup.LayoutParams.WRAP_CONTENT;
int mp = ViewGroup.LayoutParams.MATCH_PARENT;
root.addView(spaceTop, lp(0, 0, 1, -1));
root.addView(title, lp(wc, wc, 0, -1));
root.addView(message, lp(mp, wc, 0, -1));
root.addView(button, lp(wc, wc, 0, Gravity.END));
root.addView(spaceBottom, lp(mp, wc, 1, -1));
setContentView(root);
}
private LinearLayout.LayoutParams lp(int width, int height, int weight, int gravity) {
LinearLayout.LayoutParams result = new LinearLayout.LayoutParams(width, height);
result.weight = weight;
result.gravity = gravity;
return result;
}
}
The issue is likely to be that your app has been sideloaded, i.e. not installed via the Play Store, and incompatible APKs have been manually installed on those devices.
As this is happening only on Android 4 devices after migrating to Android App Bundle, i found out a way of this after adding:-
public class App extends Application {
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); }
And in build.gradle:-
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
}
as explained in this post:-
Using android vector Drawables on pre Lollipop crash
Regarding the second question :-
Resource Not Found error res/drawable/abc_switch_thumb_material.xml after adding SwitchCompat in Android App Bundle
As this is happening on all Android versions. I Sideloaded the Apk and able to reproduce the same error in logcat , So this can only be fixed by removing the SwitchCompat from my project , i know its a temporary fix and Google should surely do something about it so that at least crash does not happen after sideloading the apk, maybe redirect to play store would be the better option. But crashing of the app after migrating to Android App Bundle is definitely affects the stability of the app as many users do it on regular basis.
You can check users sharing (sideloading) the app from play console
Login play console select your app on which you publish app bundle instead of apk.
Select Android Vital -> ANRs & crashes and click on crashes tab.
select install from play
For Internal testing we cant side load the aab file apks. There only two ways give apk for user before releasing to production.
1) we have to give universal.apk file checking purpose
java -jar bundletool-all-0.12.0.jar build-apks --bundle=(build path)debug.aab --output=(output path)debug.apks --mode=universal
2) we have to upload aab file to playstore then first release for internal testing if every thing works fine then release it production.
I faced the same issue my app was getting crashes where it was unable to locate fonts and audio files from resources. the issue was that I updated the Gradle URL from 4.10.1 to 5.6.4 in Gradle-wrapper.properties but didn't update the libraries. when I reverted back to 4.10.1 it started working normally.
This question has been asked severally and only suggestions are made. Ill comprehend every suggestion hopefully.
the dependency is defined in the manifest
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version"/>
<meta-data
android:name="com.google.android.gms.vision.DEPENDENCIES"
android:value="ocr" />
and added on the app level
compile 'com.google.android.gms:play-services-vision:9.8.0'
The test device has sufficient storage which is greater than 10% of the internal and also has a very good internet connection. Permissions are also clearly defined
We start the TextRecognizer to detect text and we get our null response:
TextRecognizer textRecognizer = new TextRecognizer.Builder(getContext()).build();
if (!textRecognizer.isOperational()) {
Log.w("Main Activity", "Dependencies are not yet available");
Toast.makeText(getContext(), "Cannot Detect", Toast.LENGTH_LONG).show();
if(((MainActivity) getActivity()).hasLowStorage()) {
Toast.makeText(getContext(), "Low Storage", Toast.LENGTH_LONG).show();
Log.w("Custom_Storage", "Low Storage");
}
}
Most suggestions are to use a lower dependency compile 'com.google.android.gms:play-services:7.8+' but it doesn't work for everyone. After publishing the app, some users cant use the app.
The suggestions are not solving the problem.
Similar questions:
TextRecognizer isOperational API always returns false and
detector.isOperational() always false on android
TextRecognizer API is required to download few dependency files. Usually it is done at the time of installation but sometimes it take longer time. App will automatically download those files. Wait some time to download those files. Until download is complete TextRecognizer.isOperational will return false. After the doanlowd is completed TextRecognizer.isOperational will return true.
Even I had the same problem. I just created a new project and copied and installed dll again. Now it is working.
I'm using Cordova v4.1.2. The app uses media volume by default, and I want it to use the ringer volume for the sounds it plays. (Like in WhatsApp)
I used setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);in the onCreate() function. But it gives an error.
This is my CordovaApp.java. (in platforms\android\src\com\XX\XX)
import android.os.Bundle;
import org.apache.cordova.*;
public class CordovaApp extends CordovaActivity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
super.init();
// Set by <content src="index.html" /> in config.xml
loadUrl(launchUrl);
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
}
}
It shows the following error on running:
There is no error when I remove the line setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); from the java file, and the app runs perfectly. Any views on how to fix this?
Fixed it myself. Really easy if you were an Android geek, but anyways such a question was never asked before so I'll post the answer for others running into this same trouble.
I was right in changing the audio stream, but I was changing that in the wrong file! Doh!
This is where you should change it..
\platforms\android\CordovaLib\src\org\apache\cordova\CordovaActivity.java
at line 351 change it to setVolumeControlStream(AudioManager.STREAM_RING);
If you want to use the ringer volume though.
If you build and press the hardware volume keys, it will change and appear to use the ringer volume of course. But my case was a bit different.
I was using the cordova Media plugin org.apache.cordova.media. So when I play an audio using this plugin, it re-wires the stream back to media stream (STREAM_MUSIC). I was back to ground zero. The idea is to re-wire the plugin itself to use the audio stream of your choice. No rocket science, just change 2 lines in 2 files.
File:
\platforms\android\src\org\apache\cordova\media\AndroidHandler.java
Line 383:
setVolumeControlStream(AudioManager.STREAM_RING);
File:
\platforms\android\src\org\apache\cordova\media\AudioPlayer.java
Line 526:
setVolumeControlStream(AudioManager.STREAM_RING);
And you're good to go. Remember to replace STREAM_RING with your desired audio stream.
I know there are several Qs here that ask if its possible to add badges to an android app and they all end up with a NO answer...
But somehow the latest Facebook beta version for Android seems to do something which at least look like a badge even if it is not technically exactly that.
In that post one of the commenters says that it is somehow related to TouchWiz.
And also here they mention it as a feature of the "S3 TouchWiz Jelly Bean Addon".
I still would appreciate information on how does this can be done and if there is some API for this that I can use in my own app (when running in an appropriate environment - i.e. the same device where FB demonstrates this behavior) ?
Hi you can use this lib simply.
Support : Sony,Samsung,LG,HTC,Xiaomi,ASUS,ADW,APEX,NOVA,Huawei,ZUK,OPPO
ShortcutBadger
Add :
int badgeCount = 1;
ShortcutBadger.applyCount(context, badgeCount);
Remove :
ShortcutBadger.applyCount(context, 0);
I have figured out how this is done for Sony devices.
I've blogged about it here. I've also posted a seperate SO question about this here.
Sony devices use a class named BadgeReciever.
Declare the com.sonyericsson.home.permission.BROADCAST_BADGE permission in your manifest file:
Broadcast an Intent to the BadgeReceiver:
Intent intent = new Intent();
intent.setAction("com.sonyericsson.home.action.UPDATE_BADGE");
intent.putExtra("com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME", "com.yourdomain.yourapp.MainActivity");
intent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE", true);
intent.putExtra("com.sonyericsson.home.intent.extra.badge.MESSAGE", "99");
intent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", "com.yourdomain.yourapp");
sendBroadcast(intent);
Done. Once this Intent is broadcast the launcher should show a badge on your application icon.
To remove the badge again, simply send a new broadcast, this time with SHOW_MESSAGE set to false:
intent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE", false);
I've excluded details on how I found this to keep the answer short, but it's all available in the blog. Might be an interesting read for someone.
There is not a standard way to achieve this; Many makers such as Sony or Samsung have implemented it in their own Android customization.
For example in Samsung, you have to broadcast an intent with BADGE_COUNT_UPDATE action, let MainActivity be your main activity class and count be the number you want to display in your app icon, note that 0 will hide the badge:
Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE");
intent.putExtra("badge_count", count);
intent.putExtra("badge_count_package_name", context.getPackageName());
intent.putExtra("badge_count_class_name", MainActivity.class.getName());
context.sendBroadcast(intent);
Sony devices uses "com.sonyericsson.home.action.UPDATE_BADGE" action with their custom extras as #Marcus Answered, so you have to add "com.sonyericsson.home.permission.BROADCAST_BADGE" permission to your app manifest and:
Intent intent = new Intent("com.sonyericsson.home.action.UPDATE_BADGE");
intent.putExtra("com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME", MainActivity.class.getName());
intent.putExtra("com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE", true);
intent.putExtra("com.sonyericsson.home.intent.extra.badge.MESSAGE", String.valueOf(count));
intent.putExtra("com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME", context.getPackageName());
context.sendBroadcast(intent);
Note: it's desirable to query your app's data (context.getPackageName(), MainActivity.class.getName()) rather than hardcode it just in case you do some refactoring in the future.
But somehow the latest Facebook beta version for android does just that...
Not according to the forum thread that contains the screenshot that you linked to. Quoting vakama94:
Well, that's actually TouchWiz and not just the app. I have a Galaxy S II running JellyBean 4.1.2 and it makes the same thing but with some other applications
Whether Samsung has a public API to allow apps to publish numbers to be used as badges, I cannot say. This could be something that they did privately with a few firms.
You are welcome to provide evidence of seeing these badges on a stock Android home screen, such as one of the Nexus series devices.
I answer to this assuming that some flutter dev can search this...
In Flutter, you can achieve this by using
Flutter app badger library.
It is as simple as
FlutterAppBadger.updateBadgeCount(1); // show
FlutterAppBadger.removeBadge(); // hide