Calling startIntentSenderForResult from Fragment (Android Billing v3)
AndroidAndroid FragmentsAndroid ActivityAndroid BillingAndroid Problem Overview
The new Android Billing v3 documentation and helper code uses startIntentSenderForResult()
when launching a purchase flow. I want to start a purchase flow (and receive the result) from a Fragment
.
For example the documentation suggests calling
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
and the helper code calls
mHelper.launchPurchaseFlow(this, SKU_GAS, 10001,
mPurchaseFinishedListener, "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
which calls startIntentSenderForResult()
.
The problem is, calling startIntentSenderForResult()
causes onActivityResult()
to be called on the parent Activity
rather than on the Fragment
that it was called from (where the IabHelper
resides).
I could receive the onActivityResult()
in the parent Activity
and then manually call the onActivityResult()
on the Fragment
, but is there a way to make a call to startIntentSenderForResult()
from a Fragment
that returns the result directly to that Fragment
's onActivityResult()
?
Android Solutions
Solution 1 - Android
I suggest two solutions:
1.) Put the IabHelper mHelper on the activity and call the IabHelper from the fragment.
Something like:
To use this solution, Declare IabHelper as public in the activity and use a method to call the launcher from the Fragment.
public class MyActivity extends Activity{
public IabHelper mHelper
public purchaseLauncher(){
mHelper.launchPurchaseFlow(this, SKU_GAS, 10001,
mPurchaseFinishedListener, "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
}
/*The finished, query and consume listeners should also be implemented in here*/
}
public class FragmentActivity extends Fragment{
MyActivity myAct = (MyActivity) getActivity();
myAct.purchaseLauncher();
}
2.) In onActivityResult, call the appropriate fragment that contains the IabHelper object. Appropriate fragment can have an access method to the helper object.
protected void onActivityResult(int requestCode, int resultCode,Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag("YourTag");
if (fragment != null)
{
((MyFragmentWithIabHelper)fragment).onActivityResult(requestCode, resultCode,data);
}
}
Solution 2 - Android
-
You should modify your resultCode (RC_REQUEST) to put fragment index to it.
int rc_reqest = RC_REQUEST + ((getActivity().getSupportFragmentManager().getFragments().indexOf(this)+1)<<16) ;
mHelper.launchPurchaseFlow(getActivity(), sku, rc_reqest ,mPurchaseFinishedListener, payload); -
in IabHelper.launchPurchaseFlow(...)
change mRequestCode = requestCode
to
mRequestCode = requestCode&0xffff;
Solution 3 - Android
From SDK 24 and above, there is a startIntentSenderForResult method available in support Fragment also, which works as intended. Note that there is an additional Bundle parameter, which can be passed as null. Thus, final code will be:
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0), null);
Of course, for API 23 and below, we will still need to use the tricks described in other answers.
Solution 4 - Android
Regarding LEO's very helpful 2nd solution above:
If Google ever fixes the issue with startIntentSenderForResult and it now correctly routes the onActivityResult call to the fragment, then this solution should be future-proofed so that the fragment's onActivityResult doesn't get called twice.
I would like to propose the following modified solution proposed by LEO.
In the Fragment's parent Activity implementation:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
boolean handled = false;
// The following is a hack to ensure that the InAppPurchasesFragment receives
// its onActivityResult call.
//
// For more information on this issue, read here:
//
// http://stackoverflow.com/questions/14131171/calling-startintentsenderforresult-from-fragment-android-billing-v3
//
// Note: If Google ever fixes the issue with startIntentSenderForResult() and
// starts forwarding on the onActivityResult to the fragment automatically, we
// should future-proof this code so it will still work.
//
// If we don't do anything and always call super.onActivityResult, we risk
// having the billing fragment's onActivityResult called more than once for
// the same result.
//
// To accomplish this, we create a method called checkIabHelperHandleActivityResult
// in the billing fragment that returns a boolean indicating whether the result was
// handled or not. We would just call Fragment's onActivityResult method, except
// its return value is void.
//
// Then call this new method in the billing fragment here and only call
// super.onActivityResult if the billing fragment didn't handle it.
if (inAppPurchasesFragment != null)
{
handled = inAppPurchasesFragment.checkIabHelperHandleActivityResult(requestCode, resultCode, data);
}
if (!handled)
{
super.onActivityResult(requestCode, resultCode, data);
}
}
Then in your IAB Fragment's implementation:
/**
* Allow the IabHelper to process an onActivityResult if it can
*
* @param requestCode The request code
* @param resultCode The result code
* @param data The data
*
* @return true if the IABHelper handled the result, else false
*/
public boolean checkIabHelperHandleActivityResult(int requestCode, int resultCode, Intent data)
{
return (iabHelper != null) && iabHelper.handleActivityResult(requestCode, resultCode, data);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (!checkIabHelperHandleActivityResult(requestCode, resultCode, data))
{
super.onActivityResult(requestCode, resultCode, data);
}
}
Solution 5 - Android
I suggest creating some sort of generic handling of this issue in your base activity class if you have access to it.
For example:
public abstract class BaseActivity extends Activity {
private List<ActivityResultHandler> mResultHandlers
= new ArrayList<ActivityResultHandler>();
public void registerActivityResultHandler(ActivityResultHandler resultHandler) {
mResultHandlers.add(resultHandler);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
for (ActivityResultHandler resultHandler : mResultHandlers) {
resultHandler.handle();
}
}
}
Of course, you'll need to implement ActivityResultHandler interface by your fragments and register them on activity startup.
Solution 6 - Android
Edit: android.support.v4.app.Fragment
now contains a backwards compatible version of startIntentSenderForResult()
, so this answer is obsolete.
Old answer:
As of support library 23.2.0, modifying the requestCode
no longer works: FragmentActivity
now keeps track of the requests made by its fragments. I added this method to the FragmentActivity
that was hosting the Fragment
(code based on FragmentActivity.startActivityFromFragment(Fragment, Intent, int, Bundle)
):
public void startIntentSenderFromFragment(Fragment fragment, IntentSender intent, int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException {
if (requestCode == -1) {
startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags);
return;
}
if ((requestCode & 0xffff0000) != 0) {
throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
}
try {
Method method = FragmentActivity.class.getDeclaredMethod("allocateRequestIndex", Fragment.class);
method.setAccessible(true);
int requestIndex = (int) method.invoke(this, fragment);
startIntentSenderForResult(intent, ((requestIndex + 1) << 16) + (requestCode & 0xffff), fillInIntent, flagsMask, flagsValues, extraFlags);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
When calling this, only the passed Fragment
will receive the onActivityResult()
call.
Solution 7 - Android
You need to pass fragment and data to parent activity, then call the fragment from onActivityResult in parent activity.
like this
in fragment:
HomeActivity activity = (HomeActivity) getActivity();
activity.purchaseLauncher(this, mHelper, productDTO.getSku(), RC_REQUEST, mPurchaseFinishedListener, PAYLOAD);
in parent activity:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (storeFragment != null) {
storeFragment.onActivityResult(requestCode, resultCode, data);
}
}
public void purchaseLauncher(StoreFragment storeFragment, IabHelper mHelper, String sku, int requestCode, IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener, String payload) {
this.storeFragment = storeFragment;
mHelper.launchPurchaseFlow(this, sku, requestCode, mPurchaseFinishedListener, payload);
}
Solution 8 - Android
if (requestCode == RC_REQUEST)
{
Intent intent = new Intent(ContainerAvtivity.this,ContainerAvtivity.class);
startActivity(intent);
finish();
}
RC_REQUEST
is same as you used to launch purchase flow
Add this in the onActivityResult
of your Activity.The inventory listener will produce the desired result for you.(I know its a temp fix but worked for me)).
Solution 9 - Android
In my case i did onActivityResult in Activity :
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
}
else {
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}
and same in fragment and it makes in app billing works
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
}
else {
Log.d(ITEM_SKU, "onActivityResult handled by IABUtil.");
}
}
Solution 10 - Android
if you want to get callback on your fragment than call super.onActivityResult()
from your activity.
This will call your fragments onActivityResult()
.
And don't forget to call startIntentSenderForResult
from your fragment context.
Don't use activity context getActivity().startIntentSenderForResult
Solution 11 - Android
You need to call
super.onActivityResult(requestCode, resultCode, data);
at the beginning of your Activity's and Fragment's onActivityResult to cascade the Results to the fragments.
In my FragmentActivity this reads as
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// No action here, call super to delegate to Fragments
super.onActivityResult(requestCode, resultCode, data);
}