Calling startIntentSenderForResult from Fragment (Android Billing v3)
Asked Answered
G

11

55

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()?

Golanka answered 3/1, 2013 at 0:0 Comment(1)
any workaround for that issue? I'm facing the same problem here, can't find any solution other than the hacky ones below...Vulnerable
G
36

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);
    } 
}
Goren answered 3/1, 2013 at 1:24 Comment(8)
I am currently doing the second method, however I would rather my activity not know about the inner workings of the fragment and require plumbing in order for it to work. Right now the activity is simply a container for different billing fragments. Which fragment is used is based on which billing implementation I'm building for (e.g. Google Play, Samsung Apps).Golanka
It would be nice if there were a way for the result to automatically be directed to the fragment's onActivityResult(). Akin to calling startActivityForResult() from a fragment rather than calling getActivity().startActivityForResult().Golanka
@Golanka it will be automatically directed if you call Fragment.startActivityForResult, look - grepcode.com/file/repository.grepcode.com/java/ext/…Sukey
@RogerAlien correct, but Fragment.startActivityForResult is not being used, but rather startIntentSenderForResult() which does not mask the request code so that it is forwarded to the appropriate fragment.Golanka
If you invoked IAP through a fragment added to a child fragment manager, be sure to obtain the FragmentManager from the parent fragment through getChildFragmentManager().Mordvin
Used 1st method with ListFragment. Also fixed issue with https://mcmap.net/q/196880/-android-billing-exceptionCondescend
How to do it for startIntentSenderForResult ?Riebling
Excellent Answer.Walkabout
H
9

1) 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);

2) in IabHelper.launchPurchaseFlow(...)

change mRequestCode = requestCode

to

mRequestCode = requestCode&0xffff;
Hiawatha answered 16/3, 2014 at 9:10 Comment(4)
Do you know how to do this with normal "fragmentManager"? getActivity().getFragmentManager()...?... getFragment(?,?)Stevenage
This seems to solve the problem. Should be the accepted answer. Don't forget to add super.onActivityResult(requestCode, resultCode, data); to onActivityResult in your activity.Roseline
Unfortunately, newer libraries do not allow this solution any more, complaining about: java.lang.IllegalArgumentException: Can only use lower 16 bits for requestCodeRoseline
This solution uses the framework code that delegates results to fragments. Since this is not a public API, you can't rely on it working across all current and future versions.Benefaction
D
4

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.

Dandruff answered 23/8, 2016 at 12:31 Comment(0)
N
3

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:
    //
    // https://mcmap.net/q/195966/-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);
    }
}
Navigator answered 20/9, 2014 at 15:18 Comment(0)
T
2

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.

Typhon answered 9/8, 2014 at 20:38 Comment(0)
G
2

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.

Goodwife answered 26/2, 2016 at 18:22 Comment(0)
S
2

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);
    }
Shondrashone answered 3/4, 2017 at 11:14 Comment(0)
C
1
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)).

Criss answered 21/11, 2014 at 12:8 Comment(0)
E
0

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.");
    }

}
Elevenses answered 22/5, 2014 at 8:14 Comment(0)
A
0

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

Ain answered 21/9, 2016 at 10:28 Comment(0)
Z
-1

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);
}
Zoophobia answered 9/4, 2013 at 15:40 Comment(1)
The problem is startIntentSenderForResult() does not forward the result to the fragment that called it; only the activity's onActivityResult() is called because the request code is not masked like it is when Fragment.startActivityForResult() is called.Golanka

© 2022 - 2024 — McMap. All rights reserved.