apptentive / apptentive-android

Apptentive Android SDK
http://www.apptentive.com
BSD 3-Clause "New" or "Revised" License
65 stars 64 forks source link

In-App Review dialog| Unable to create in-app review manager | Class cast exception #216

Closed RanjithRagavan closed 3 years ago

RanjithRagavan commented 3 years ago

Android Apptentive SDK can be initialized only with the Application context and the same context is used in DefaultInAppReviewManagerFactory to cast to Activity context to pass to GooglePlayReviewManager that's leads to class cast exception.

at com.apptentive.android.sdk.external.DefaultInAppReviewManagerFactory.createReviewManager(DefaultInAppReviewManagerFactory.java:36) at com.apptentive.android.sdk.module.engagement.InAppRatingDialogInteractionLauncher.launch(InAppRatingDialogInteractionLauncher.java:46) at com.apptentive.android.sdk.module.engagement.InAppRatingDialogInteractionLauncher.launch(InAppRatingDialogInteractionLauncher.java:21) at com.apptentive.android.sdk.module.engagement.EngagementModule.launchInteraction(EngagementModule.java:135) at com.apptentive.android.sdk.module.engagement.EngagementModule$1.execute(EngagementModule.java:122) at com.apptentive.android.sdk.util.threading.DispatchTask.run(DispatchTask.java:39) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

CaseyApptentive commented 3 years ago

Hi @RanjithRagavan, thanks for the report.

We're unable to reproduce this issue. Can you tell us more?

One other thing you could try: create a blank app that contains Apptentive. Do you still see the same issue in this blank app? This would help narrow down if this is related to the environment and versioning. If you do this, feel free to send me the build at support@apptentive.com and we can help.

Thanks!

weeeBox commented 3 years ago

Hey @RanjithRagavan,

The context object should be passed as a part of Apptentive.engage method and must be an Activity in order for Google In-App review to work. Would you be able to share some implementation details?

RanjithRagavan commented 3 years ago

@CaseyApptentive the app is not crashing it shows the Apptentive Rating dialog instead of Google In-App Review. As you see the log I added in the first comment DefaultInAppReviewManagerFactory is handling the flow as if not able to show the Google In-App review it defaults to Apptentive Rating dialog.

@weeeBox So how its implemented in the project is Apptentive is initialized in Application class with Application contest Apptentive.register(this, BuildConfig.APPTENTIVE_API_KEY_ID, BuildConfig.APPTENTIVE_SIGNATURE_KEY_ID);

And for enage passing activity context as Apptentive.engage(activityContext, "some event");

weeeBox commented 3 years ago

@RanjithRagavan, can you confirm that activityContext is an Activity type?

RanjithRagavan commented 3 years ago

@weeeBox yes the activityContext is of type Activity actually its fragment setting getActivity() context

BartlomiejSoryAutomatic commented 3 years ago

In previous versions it was fine to pass the application context as Apptentive.engage(context, "some event"); In that case it would just launch Apptentive Interaction in a new task, exactly as the docs say.

It seems that it will no longer no longer be possible in the newer version ?

weeeBox commented 3 years ago

@BartlomiejSoryAutomatic, Google's ReviewManager requires an Activity object to be passed to the launchReviewFlow method (see details here). We don't have an access to any of the activities from the host application so we had to assume the passed context is an Activity type (or fail the process otherwise). As a workaround, we can try to keep track of all the Activity objects in the host app using Application.ActivityLifecycleCallbacks but this tends to be risky and error-prone. The best-case scenario is for you to explicitly pass an Activity object into Apptentive.engage() calls while we try to figure out a better solution.

weeeBox commented 3 years ago

@RanjithRagavan, let me take a look at this and I'll get back to you

weeeBox commented 3 years ago

@RanjithRagavan, would you be able to put a breakpoint at DefaultInAppReviewManagerFactory:36 and tell us what type the context variable has? You can open DefaultInAppReviewManagerFactory in Android Studio by pressing 'Cmd+Option+Oon Mac orAlt+Shift+N` on Windows.

RanjithRagavan commented 3 years ago

@weeeBox what's the difference between Love Dialog coupled Rating Dialog vs Standalone Rating dialog. Because for the Love Dialog coupled rating dialog I can see the Play-store rating dialog coming through.

But only for the Standalone rating dialog, I see the error log and it always shows Apptentive Rating dialog. I/Apptentive: [INTERACTIONS] Launching interaction: 'InAppRatingDialog' I/Apptentive: [Apptentive Queue] [INTERACTIONS] Engage event: 'com.apptentive#InAppRatingDialog#request' E/Apptentive: [IN_APP_REVIEW] Unable to create in-app review manager java.lang.ClassCastException: Application cannot be cast to android.app.Activity at com.apptentive.android.sdk.external.DefaultInAppReviewManagerFactory.createReviewManager(DefaultInAppReviewManagerFactory.java:36) at com.apptentive.android.sdk.module.engagement.InAppRatingDialogInteractionLauncher.launch(InAppRatingDialogInteractionLauncher.java:46) at com.apptentive.android.sdk.module.engagement.InAppRatingDialogInteractionLauncher.launch(InAppRatingDialogInteractionLauncher.java:21) at com.apptentive.android.sdk.module.engagement.EngagementModule.launchInteraction(EngagementModule.java:135) at com.apptentive.android.sdk.module.engagement.EngagementModule$1.execute(EngagementModule.java:122) at com.apptentive.android.sdk.util.threading.DispatchTask.run(DispatchTask.java:39) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

weeeBox commented 3 years ago

@RanjithRagavan, there should be no difference from the end-user point of view but it seems the Application context somehow sneaks in. Let me double-check that and I'll get back to you!

CaseyApptentive commented 3 years ago

Hi @RanjithRagavan and @BartlomiejSoryAutomatic ! I've shared details with your Customer Success Managers and they will be reaching out shortly to help. We may need to gather more details, and they will help walk through that process.

Thanks again, and we'll be in touch shortly.

BartlomiejSoryAutomatic commented 3 years ago

@BartlomiejSoryAutomatic, Google's ReviewManager requires an Activity object to be passed to the launchReviewFlow method (see details here). We don't have an access to any of the activities from the host application so we had to assume the passed context is an Activity type (or fail the process otherwise). As a workaround, we can try to keep track of all the Activity objects in the host app using Application.ActivityLifecycleCallbacks but this tends to be risky and error-prone. The best-case scenario is for you to explicitly pass an Activity object into Apptentive.engage() calls while we try to figure out a better solution.

@weeeBox If the Apptentive.engage() requires the activity as a parameter, then it should no longer allow to pass in a Context parameter but an Activity instead

weeeBox commented 3 years ago

@BartlomiejSoryAutomatic, this could be the case but we can't just introduce a breaking change since it would make all the existing clients non-compilable.

BartlomiejSoryAutomatic commented 3 years ago

What is wrong with using Application.ActivityLifecycleCallbacks ? Keeping track of a currently created and resumed activity and using it for Google's ReviewManager seems like a reasonable approach for this situation

weeeBox commented 3 years ago

@BartlomiejSoryAutomatic, we can't make any assumption on the host application structure. Imagine a situation when you have a "popup" activity and when you press a button - you will engage an interaction and finish the activity immediately. The lifecycle callback will capture the "popup" Activity and pass it into the SDK's engage method then make and an async call to Google API and try to use the captured Activiity object in the callback (at this point the Activity is already finished) - we might end up with undefined behavior or a runtime crash.

BartlomiejSoryAutomatic commented 3 years ago

I see your point, thanks for clarifying. However isn' t this scenario also possible when passing an activity as engage method parameter ? Here, that activity can also be closed right away after triggering an interaction (for example user clicks Back button) which could result in the same situation IMHO.

weeeBox commented 3 years ago

@BartlomiejSoryAutomatic, it's possible but in the latter case, you have more control over the execution flow and may postpone finishing an Activity until after the engagement callback is fired.

BartlomiejSoryAutomatic commented 3 years ago

On the other hand isn't the google in-app review api aware of such possibility and does not check if the caller activity hasn't finished/was destroyed before showing the dialog ?

weeeBox commented 3 years ago

Can't tell for sure: haven't checked their source code yet but in terms of results - pretty much the same: the request will fail and the fallback mechanism would kick in.

RanjithRagavan commented 3 years ago

@weeeBox its the context issue in my application instead of activity context application context is passed. Closing the issue.