La nouvelle documentation Android Billing v3 et le code d'assistance utilisent startIntentSenderForResult()
lors du lancement d'un flux d'achat. Je veux commencer un flux d'achat (et recevoir le résultat) à partir d'une Fragment
.
Par exemple, la documentation suggère d’appeler
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
et les appels helper code
mHelper.launchPurchaseFlow(this, SKU_GAS, 10001,
mPurchaseFinishedListener, "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
qui appelle startIntentSenderForResult()
.
Le problème est que l'appel de startIntentSenderForResult()
provoque l'appel de onActivityResult()
sur le parent Activity
plutôt que sur la Fragment
à partir de laquelle il a été appelé (où réside le IabHelper
).
Je pourrais recevoir la onActivityResult()
dans le parent Activity
et ensuite appeler manuellement la onActivityResult()
sur la Fragment
, mais existe-t-il un moyen de passer un appel à startIntentSenderForResult()
à partir d'une Fragment
qui renvoie directement le résultat à cette Fragment
's onActivityResult()
?
Je suggère deux solutions:
1.) Mettez l’IabHelper mHelper sur l’activité et appelez le IabHelper à partir du fragment.
Quelque chose comme:
Pour utiliser cette solution, déclarez IabHelper comme public dans l'activité et utilisez une méthode pour appeler le programme de lancement à partir du 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.) Dans onActivityResult, appelez le fragment approprié contenant l'objet IabHelper. Le fragment approprié peut avoir une méthode d'accès à l'objet d'assistance.
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);
}
}
1) Vous devez modifier votre code de résultat (RC_REQUEST) pour y mettre un index de fragment.
int rc_reqest = RC_REQUEST + ((getActivity().getSupportFragmentManager().getFragments().indexOf(this)+1)<<16) ;
mHelper.launchPurchaseFlow(getActivity(), sku, rc_reqest ,mPurchaseFinishedListener, payload);
2) dans IabHelper.launchPurchaseFlow (...)
change mRequestCode = requestCode
à
mRequestCode = requestCode&0xffff;
En ce qui concerne la 2ème solution très utile de LEO ci-dessus:
Si Google corrige le problème avec startIntentSenderForResult et achemine correctement l'appel onActivityResult vers le fragment, cette solution doit être prête pour l'avenir de sorte que Du fragment ne soit pas appelé deux fois.
Je voudrais proposer la solution modifiée suivante proposée par LEO.
Dans l'implémentation de l'activité parent du fragment:
@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);
}
}
Ensuite, dans l'implémentation de votre fragment 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);
}
}
Vous devez transmettre fragment et data à l'activité parent, puis appeler fragment onActivityResult à partir de l'activité parent.
comme ça
en fragment:
HomeActivity activity = (HomeActivity) getActivity();
activity.purchaseLauncher(this, mHelper, productDTO.getSku(), RC_REQUEST, mPurchaseFinishedListener, PAYLOAD);
en activité parentale:
@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);
}
À partir du SDK 24 et des versions ultérieures, il existe une méthode startIntentSenderForResult disponible dans le support Fragment également, qui fonctionne comme prévu. Notez qu’il existe un paramètre Bundle supplémentaire, qui peut être passé comme null. Ainsi, le code final sera:
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0), null);
Bien sûr, pour l’API 23 et les versions ultérieures, nous aurons toujours besoin des astuces décrites dans d’autres réponses.
Je suggère de créer une sorte de traitement générique de ce problème dans votre classe d'activité de base si vous y avez accès.
Par exemple:
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();
}
}
}
Bien entendu, vous devrez implémenter l'interface ActivityResultHandler par vos fragments et les enregistrer au démarrage de l'activité.
Edit: Android.support.v4.app.Fragment
contient maintenant une version rétro-compatible de startIntentSenderForResult()
; cette réponse est donc obsolète.
Ancienne réponse:
Depuis la bibliothèque de support 23.2.0, la modification de requestCode
ne fonctionne plus: FragmentActivity
garde maintenant une trace des demandes effectuées par ses fragments. J'ai ajouté cette méthode à la FragmentActivity
qui hébergeait la Fragment
(code basé sur 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);
}
}
En appelant ceci, seule la Fragment
passée recevra l'appel onActivityResult()
.
if (requestCode == RC_REQUEST)
{
Intent intent = new Intent(ContainerAvtivity.this,ContainerAvtivity.class);
startActivity(intent);
finish();
}
RC_REQUEST
est identique à celui utilisé pour lancer le flux d'achat
Ajoutez ceci dans la onActivityResult
de votre activité.L’auditeur d’inventaire produira le résultat souhaité pour vous.
si vous souhaitez obtenir un rappel sur votre fragment, appelez super.onActivityResult()
depuis votre activité.
Cela appellera vos fragments onActivityResult()
.
Et n'oubliez pas d'appeler startIntentSenderForResult
à partir du contexte de votre fragment.
N'utilisez pas le contexte d'activité getActivity().startIntentSenderForResult
Vous devez appeler
super.onActivityResult(requestCode, resultCode, data);
au début de votre activité et de vos fragments onActivityResult pour cascade les résultats avec les fragments.
Dans mon FragmentActivity, cela se lit comme
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// No action here, call super to delegate to Fragments
super.onActivityResult(requestCode, resultCode, data);
}
Dans mon cas, j'ai fait 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.");
}
}
et même en fragment et il fait dans la facturation d'applications fonctionne
@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.");
}
}