it-swarm.com.ru

Вызов startIntentSenderForResult из фрагмента (Android Billing v3)

Новая документация и вспомогательный код Android Billing v3 использует startIntentSenderForResult() при запуске потока покупок. Я хочу начать процесс покупки (и получить результат) с Fragment.

Например, документация предлагает позвонить

startIntentSenderForResult(pendingIntent.getIntentSender(),
    1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
    Integer.valueOf(0));

и код помощника звонки

mHelper.launchPurchaseFlow(this, SKU_GAS, 10001,   
    mPurchaseFinishedListener, "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");

который вызывает startIntentSenderForResult().

Проблема в том, что при вызове startIntentSenderForResult()onActivityResult() вызывается для родительского Activity, а не для Fragment, из которого он был вызван (где находится IabHelper).

Я мог бы получить onActivityResult() в родительском Activity и затем вручную вызвать onActivityResult() для Fragment, но есть ли способ сделать вызов startIntentSenderForResult() из Fragment, который возвращает результат непосредственно Fragment's onActivityResult()?

53
ashughes

Я предлагаю два решения:

1.) Поместите IabHelper mHelper на упражнение и вызовите IabHelper из фрагмента.

Что-то вроде:

Чтобы использовать это решение, объявите IabHelper как общедоступный в действии и используйте метод для вызова средства запуска из фрагмента.

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.) В onActivityResult вызовите соответствующий фрагмент, который содержит объект IabHelper. Соответствующий фрагмент может иметь метод доступа к вспомогательному объекту.

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);
    } 
}
36
LEO

1) Вы должны изменить свой resultCode (RC_REQUEST), чтобы добавить к нему индекс фрагмента.

int rc_reqest = RC_REQUEST +  ((getActivity().getSupportFragmentManager().getFragments().indexOf(this)+1)<<16)  ;      
mHelper.launchPurchaseFlow(getActivity(), sku, rc_reqest ,mPurchaseFinishedListener, payload);

2) в IabHelper.launchPurchaseFlow (...)

change mRequestCode = requestCode

в 

mRequestCode = requestCode&0xffff;
9
Timur

Что касается второго полезного решения LEO, приведенного выше:

Если Google когда-либо исправит проблему с помощью startIntentSenderForResult и теперь он правильно направляет вызов onActivityResult к фрагменту, то это решение должно быть ориентировано на будущее, чтобы OnActivityResult фрагмента не вызывался дважды.

Я хотел бы предложить следующее модифицированное решение, предложенное LEO.

В реализации действия родительского фрагмента:

@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);
    }
}

Затем в реализации вашего фрагмента IAB:

/**
 * 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);
    }
}
3
Michael Krause

Вам нужно передать фрагмент и данные родительской активности, а затем вызвать фрагмент onActivityResult из родительской активности.

как это

во фрагменте:

HomeActivity activity = (HomeActivity) getActivity();
activity.purchaseLauncher(this, mHelper, productDTO.getSku(), RC_REQUEST, mPurchaseFinishedListener, PAYLOAD);

в родительской деятельности:

@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);
    }

2
ayda

Начиная с SDK 24 и выше, есть startIntentSenderForResult метод, также доступный в поддержке Fragment, который работает по назначению. Обратите внимание, что есть дополнительный параметр Bundle, который можно передать как ноль. Таким образом, окончательный код будет:

startIntentSenderForResult(pendingIntent.getIntentSender(),
    1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
    Integer.valueOf(0), null);

Конечно, для API 23 и ниже нам все еще нужно будет использовать приемы, описанные в других ответах.

2
User31689

Я предлагаю создать какую-то общую обработку этой проблемы в вашем базовом классе деятельности, если у вас есть доступ к нему.

Например:

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();
        }
    }
}

Конечно, вам нужно реализовать интерфейс ActivityResultHandler по вашим фрагментам и зарегистрировать их при запуске активности.

2
Leonid Kaganovskyy

Правка: Android.support.v4.app.Fragment теперь содержит обратно совместимую версию startIntentSenderForResult(), поэтому этот ответ устарел.

Старый ответ:

Начиная с библиотеки поддержки 23.2.0, изменение requestCode больше не работает: FragmentActivity теперь отслеживает запросы, сделанные его фрагментами. Я добавил этот метод к FragmentActivity, на котором размещалась Fragment (код, основанный на 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);
    }
}

При вызове этого вызова только Fragment получит вызов onActivityResult().

2
Jarett Millard
if (requestCode == RC_REQUEST) 
{
    Intent intent = new Intent(ContainerAvtivity.this,ContainerAvtivity.class);
    startActivity(intent);
    finish();
}

RC_REQUEST такой же, как вы использовали для запуска потока покупок

Добавьте это в onActivityResult вашей Деятельности. Слушатель инвентаризации даст желаемый результат для вас (я знаю, это временное исправление, но он работал для меня)).

1
Arun Salaria

если вы хотите получить обратный вызов для вашего фрагмента, чем вызывать super.onActivityResult() из вашей деятельности.

Это будет называть ваши фрагменты onActivityResult().

И не забудьте вызвать startIntentSenderForResult из контекста вашего фрагмента.

Не используйте контекст активности getActivity().startIntentSenderForResult

0
Nikhil Jain

Вам нужно позвонить

super.onActivityResult(requestCode, resultCode, data);

в начале вашего действия и фрагмента onActivityResult для каскадирования результатов к фрагментам.

В моем FragmentActivity это читается как

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // No action here, call super to delegate to Fragments
    super.onActivityResult(requestCode, resultCode, data);
}
0
user2262061

В моем случае я сделал onActivityResult в 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.");
    }

}

и то же во фрагменте, и это делает в приложении биллинговых работ 

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

}
0
Penzzz