Cannot read Parcelable from Intent in onActivityReenter - android

I want to return my address model as result in Intent. If I try get my address model in onAcivityResult methods all works fine, but in onActivityReenter I got this Exception:
Class not found when unmarshalling: ua.com.uklontaxi.models.UIAddress
java.lang.ClassNotFoundException: ua.com.myapp.models.UIAddress
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:324)
at android.os.Parcel.readParcelableCreator(Parcel.java:2383)
at android.os.Parcel.readParcelable(Parcel.java:2337)
at android.os.Parcel.readValue(Parcel.java:2243)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2592)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.Bundle.getParcelable(Bundle.java:786)
There are not such errors in another places (for example onActivityResult in the same Activity)
How to fix it?
P.S. I put my model to Bundle and then I put this bundle to Intent. I tried put address to Intent without Bundle-wrapping. It doens't help me.

This helps me:
override fun onActivityReenter(resultCode: Int, data: Intent?) {
super.onActivityReenter(resultCode, data)
data?.setExtrasClassLoader(this.classLoader) // this is context!
}

For anyone still wondering why this issue persists only for API versions below Oreo (8), it has to do with a fix that was provided for API 8+. You can find the official fix here or you can check the below code (taken from the official repo):
Intent intent = mEnterActivityOptions.getResultData();
if (intent != null) {
intent.setExtrasClassLoader(activity.getClassLoader());
}
activity.onActivityReenter(result, intent);
It essentially, adds manually the ClassLoader to the Intent, before calling the Activity's onActivityReenter(...);

Related

Autofill service enable via Activity Results API

I'm try to register an ActivityResultContract for android.provider.Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE using kotlin and AndroidX:
class AutofillContract() : ActivityResultContract<Any?,ActivityResult>() {
override fun createIntent(context: Context, input: Any?): Intent
= Intent(android.provider.Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
override fun parseResult(resultCode: Int, intent: Intent?): ActivityResult
= ActivityResult(resultCode, intent)
}
val afrl = registerForActivityResult(AutofillContract()) {
if (it.resultCode == RESULT_OK) ...
else ...
}
However, when I try to use it:
afrl.launch(AutofillContract().createIntent(this, null))
I get IllegalArgumentException: Can only use lower 16 bits for requestCode, which I presume was triggered by an internal startActivityForResult() call.
I haven't used a custom ActivityResultContract before, and although it seems simple it also seems a bit sketchy to me -- I'm not sure if regarding the input as irrelevant (Any?) is the way to go, but it does seem irrelevant in this case (the first version used Intent as the input type but there doesn't seem to be a point, and the problem, "Can only use lower 16 bits..." was the same).
I'm using androidx.activity:activity-ktx:1.2.0-alpha08.
As per this issue, you get that error when you are using an older version of Fragments.
You must also upgrade your version of Fragments to androidx.fragment:fragment-ktx:1.3.0-alpha08.

startActivityForResult crashes while startActivity works

I have an activity that I'm starting:
Intent intent = new Intent(this, CreateItemDetailsActivity.class);
if(storeId != null) {
intent.putExtra(Identifiers.STORE_ID, storeId);
}
intent.putExtra(Identifiers.ITEM_NAME, name);
intent.putExtra(Identifiers.ITEM_DESCRIPTION, description);
startActivity(intent);
This works, but now I need to return data to the original activity, so I change startActivity call to:
startActivityForResult(intent, CREATE_ITEM_RESULT);
(CREATE_ITEM_RESULT is just a random integer number 63463657 I made up)
However, my app crashes without neither onCreate methods (I've implemented and put a breakpoint in both) being called:
Uncaught exception in thread main: java.lang.IllegalStateException: Could not execute method for android:onClick
android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:389)
(the code is called in a button click handler, hence onClick)
I've seen App crashes when calling startActivityForResult which has the same problem but the accepted answer suggests removing a line, recipe.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);, which I don't have.
Why am I getting a crash on startActivityForResult while startActivity works perfectly?
(My min SDK level is 16, target and compile SDK levels are 27, and I'm running Android 8.1 emulator)
If have not seen any documentation on this issue, but try a four digit int value should get rid of the error.
I have read Google docs for startActivityForResult and I have not found any reference to a maximum int size. Even the code documentation states:
#param requestCode If >= 0, this code will be returned in the
onActivityResult() when the activity exists
So, no mention of a maximum int value. Odd...
EDIT
Thanks to #MidasLefko for finding this information:
RequestCodes can only be a max of 0xffff (65535). So you are probably
calling startActivityForResult(intent, REQUEST_CODE); and REQUEST_CODE
is greater than 65535.
on this website:
https://code-examples.net/en/q/db5947
In the non-support Activity class implementation of startActivityForResult there is no restriction on the maximum int for requestCode, as long as it is positive. However FragmentActivity (and by extension AppCompatActivity) overrides this behavior (documentation):
void startActivityForResult (Intent intent, int requestCode)
Modifies the standard behavior to allow results to be delivered to fragments. This imposes a restriction
that requestCode be <= 0xffff.
In the source code of FragmentActivity one finds the following helpful comment:
A hint for the next candidate request index. Request indicies are ints
between 0 and 2^16-1 which are encoded into the upper 16 bits of the
requestCode for Fragment.startActivityForResult(...) calls. This
allows us to dispatch onActivityResult(...) to the appropriate
Fragment. Request indicies are allocated by allocateRequestIndex(...).
ints in java are 32 bit, FragmentActivity uses 16 to determine which fragment to send the result to and the other 16 are available for the developer.

Android Service cannot start because of a ClassNotFoundException from a class that doesn't even exist anymore

My activity 'used' the put a custom (serializable) class in the bundle when starting a Service. But couple version back I had remove it (and delete the class) and directly put primitives in the bundle.
And now... my ACRA log is getting completely hammered by this error of a crash in the onStartCommand when on the line the bundle is read.
java.lang.RuntimeException: Unable to start service com.xxx.MyService#db4c909 with Intent { act=ACTION_START flg=0x4 cmp=com.xxx/.MyService (has extras) }: java.lang.RuntimeException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = com.xxx.MyBundle)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3045)
at android.app.ActivityThread.access$2200(ActivityThread.java:157)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1454)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5551)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:730)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:620)
Caused by: java.lang.RuntimeException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = com.xxx.MyBundle)
at android.os.Parcel.readSerializable(Parcel.java:2491)
at android.os.Parcel.readValue(Parcel.java:2294)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2592)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.BaseBundle.get(BaseBundle.java:281)
at com.xxx.MyService.onStartCommand(MyService.java:190)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3028)
....
... 14 more
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.xxx.MyBundle" on path: DexPathList[[zip file "/data/app/com.xxx-2/base.apk"],nativeLibraryDirectories=[/data/app/com.xxx-2/lib/arm, /data/app/com.xxx-2/base.apk!/lib/armeabi-v7a, /vendor/lib, /system/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
I don't get what I'm getting error knowing that the com.xxx.MyBundle doesn't even exist anymore in the project!
Note that Im seeing this error in my crash logs. I cannot reproduce this locally probably because I have uninstalled and reinstall the app many times and this it probably fixes the problem.
The issue here is that there thousands of people which the app is probably crashing and cannot just tell to everyone "uninstall- reinstall". I'd like to find the cause of this and possibily fix it with a new update without the user having to manually uninstall the app and reinstall..
EDIT
This is the history of event
v1: I was putting a Serializable class in the bundle when starting the service
(I had forgotten to set the serialVersionUID) and I was seeing a lot of serialization error when starting the service like version mismatch when desializing.. typical error when forgetting the serialVersionUID
v2: I have added serialVersionUID=-1 in my Serializable class
(No dice, in the log I was still seeing these serialization is now saying found version={random number} but was -1....
v3: I gave up and deleted com.xx.MyBundle and created a com.xx.MyBundle2
(no Dice.. and this is when I started to see in the log that ClassNotFoundException)
v4: I Delete com.xx.MyBundle2 and instead of putting the serializable class in the bundle I put directly my primitives
So you see.. it is not a question of service running or not.. Even when my users update from v3 to v4 I would have expect this error to be gone but no... I still see the ClassNotFoundExcpetionn in v4
As I guess from your question, you might have started a Service with a previous version of your application which was removed lately along with the classes used. Its causing the ClassNotFoundException.
So, in this specific case, you might consider detecting the application update and on application update you need to check if the Service is running in background which needs to be stopped.
To check for your newer version of application is installed you'll receive a broadcast event called ACTION_MY_PACKAGE_REPLACED and then you stop the running Service.
To check if your desired Service is already running, you might consider having a checker like this.
private boolean isMyServiceRunning(Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
And then call it using
if(isMyServiceRunning(MyService.class))
stopMyService();
Update
I think I've understood your problem correctly and thought of a solution above already. I guess, the Service you started already in your v1 or v2 is running and expecting the bundle of serialized class there. So you need to stop the service and start it again to make it follow the current behaviour which you're expecting.

App crashes on building back stack

I am building in my IntentService a back stack. I am receiving crash reports from Kitkat devices.
My code is very simple and I don't know what could be the reason. I have a stacktrace but it's not getting me anywhere. Anyone experienced something like this?
override fun onHandleIntent(intent: Intent?) {
if (intent != null) {
val articleIntent = intentFor<ArticleActivity>()
articleIntent.putExtras(intent)
TaskStackBuilder.create(this).addNextIntent(intentFor<DrawerActivity>()).addNextIntent(articleIntent).startActivities()
}
}
Exception from Crashlytics
java.lang.NullPointerException
at android.os.Parcel.readException(Parcel.java:1471)
at android.os.Parcel.readException(Parcel.java:1419)
at android.app.ActivityManagerProxy.startActivities(ActivityManagerNative.java:4473)
at android.app.Instrumentation.execStartActivitiesAsUser(Instrumentation.java:1496)
at android.app.ContextImpl.startActivitiesAsUser(ContextImpl.java:1417)
at android.content.ContextWrapper.startActivitiesAsUser(ContextWrapper.java:356)
at android.app.TaskStackBuilder.startActivities(TaskStackBuilder.java:221)
at android.app.TaskStackBuilder.startActivities(TaskStackBuilder.java:232)
at android.app.TaskStackBuilder.startActivities(TaskStackBuilder.java:208)
at se.omni.gcm.OpenArticleService.onHandleIntent(OpenArticleService.kt:27)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:157)
at android.os.HandlerThread.run(HandlerThread.java:61)
I just found similar question on the SO TaskStackBuilder#startActivities() NullPointerException with answer that satisfy me. Since I started a bounty, I cannot close or delete this question.

How can I test setResult() in an Android Espresso test?

Is there any good way to test the result code and data in an Android Espresso test? I am using Espresso 2.0.
Suppose I have an Activity called BarActivity.class, which upon performing some action, calls setResult(int resultCode, Intent data) with the appropriate payload.
I'd like to write a test case to verify the resultCode and data. However, because setResult() is a final method, I can't override it.
Some options I thought about were:
Define a new method like setActivityResult() and just use that so it can be intercepted, etc...
Write a test-only TestActivity that will call startActivityForResult() on BarActivity and check the result in TestActivity.onActivityResult()
Trying to think what's lesser of the two evils, or if there's any other suggestions on how to test for this. Any suggestions? Thanks!
If meanwhile you switched to the latest Espresso, version 3.0.1, you can simply use an ActivityTestRule and get the Activity result like this:
assertThat(rule.getActivityResult(), hasResultCode(Activity.RESULT_OK));
assertThat(rule.getActivityResult(), hasResultData(IntentMatchers.hasExtraWithKey(PickActivity.EXTRA_PICKED_NUMBER)));
You can find a working example here.
If you are willing to upgrade to 2.1, then take a look at Espresso-Intents:
Using the intending API (cousin of Mockito.when), you can provide a response for activities that are launched with startActivityForResult
This basically means it is possible to build and return any result when a specific activity is launched (in your case the BarActivity class).
Check this example here: https://google.github.io/android-testing-support-library/docs/espresso/intents/index.html#intent-stubbing
And also my answer on a somewhat similar issue (but with the contact picker activity), in which I show how to build a result and send it back to the Activity which called startActivityForResult()
this works for me:
#Test
fun testActivityForResult(){
// Build the result to return when the activity is launched.
val resultData = Intent()
resultData.putExtra(KEY_VALUE_TO_RETURN, true)
// Set up result stubbing when an intent sent to <ActivityB> is seen.
intending(hasComponent("com.xxx.xxxty.ActivityB")) //Path of <ActivityB>
.respondWith(
Instrumentation.ActivityResult(
RESULT_OK,
resultData
)
)
// User action that results in "ActivityB" activity being launched.
onView(withId(R.id.view_id))
.perform(click())
// Assert that the data we set up above is shown.
onView(withId(R.id.another_view_id)).check(matches(matches(isDisplayed())))
}
Assuming a validation like below on onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
data?.getBooleanExtra(KEY_VALUE_TO_RETURN, false)?.let {showView ->
if (showView) {
another_view_id.visibility = View.VISIBLE
}else{
another_view_id.visibility = View.GONE
}
}
}
I follow this guide as reference : https://developer.android.com/training/testing/espresso/intents and also i had to check this links at the end of the above link https://github.com/android/testing-samples/tree/master/ui/espresso/IntentsBasicSample
and
https://github.com/android/testing-samples/tree/master/ui/espresso/IntentsAdvancedSample
If you're using ActivityScenario (or ActivityScenarioRule) as is the current recommendation in the Android Developers documentation (see the Test your app's activities page), the ActivityScenario class offers a getResult() method which you can assert on as follows:
#Test
fun result_code_is_set() {
val activityScenario = ActivityScenario.launch(BarActivity::class.java)
// TODO: execute some view actions which cause setResult() to be called
// TODO: execute a view action which causes the activity to be finished
val activityResult = activityScenario.result
assertEquals(expectedResultCode, activityResult.resultCode)
assertEquals(expectedResultData, activityResult.resultData)
}

Categories

Resources