son - Llamar a startIntentSenderForResult desde Fragment(Android Billing v3)
java android fragment (11)
1) Debe modificar su código de resultado (RC_REQUEST) para ponerle índice de fragmento.
int rc_reqest = RC_REQUEST + ((getActivity().getSupportFragmentManager().getFragments().indexOf(this)+1)<<16) ;
mHelper.launchPurchaseFlow(getActivity(), sku, rc_reqest ,mPurchaseFinishedListener, payload);
2) en IabHelper.launchPurchaseFlow (...)
change mRequestCode = requestCode
a
mRequestCode = requestCode&0xffff;
La nueva documentación de Android Billing v3 y el código auxiliar utilizan startIntentSenderForResult()
al iniciar un flujo de compra. Quiero comenzar un flujo de compra (y recibir el resultado) de un Fragment
.
Por ejemplo, la documentation sugiere llamar
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
y las llamadas al código auxiliar
mHelper.launchPurchaseFlow(this, SKU_GAS, 10001,
mPurchaseFinishedListener, "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
que llama a startIntentSenderForResult()
.
El problema es que al llamar a startIntentSenderForResult()
llama a startIntentSenderForResult()
en la Activity
primaria en lugar de en el Fragment
desde el que se IabHelper
(donde reside el IabHelper
).
Podría recibir onActivityResult()
en la Activity
principal y luego llamar manualmente a onActivityResult()
en el Fragment
, pero ¿hay alguna manera de hacer una llamada a startIntentSenderForResult()
desde un Fragment
que devuelve el resultado directamente a onActivityResult()
ese Fragment
onActivityResult()
?
Desde el SDK 24 y startIntentSenderForResult posteriores, hay un método startIntentSenderForResult disponible también en el Fragmento de soporte, que funciona según lo previsto. Tenga en cuenta que hay un parámetro Bundle adicional, que se puede pasar como nulo. Por lo tanto, el código final será:
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0), null);
Por supuesto, para la API 23 y las siguientes, aún necesitaremos usar los trucos descritos en otras respuestas.
En mi caso lo hice en ActivityResult en 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.");
}
}
y lo mismo en fragmento y lo hace en la facturación de la aplicación funciona
@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.");
}
}
Necesita pasar fragmento y datos a la actividad principal, luego invocar el fragmento en ActivityResult desde la actividad principal.
Me gusta esto
en fragmento:
HomeActivity activity = (HomeActivity) getActivity();
activity.purchaseLauncher(this, mHelper, productDTO.getSku(), RC_REQUEST, mPurchaseFinishedListener, PAYLOAD);
en la actividad de los padres:
@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);
}
Respecto a la segunda solución de LEO muy útil arriba:
Si Google alguna vez soluciona el problema con startIntentSenderForResult y ahora enruta correctamente la llamada onActivityResult al fragmento, esta solución debe ser a prueba de futuro para que el fragmento onActivityResult no reciba dos llamadas.
Me gustaría proponer la siguiente solución modificada propuesta por LEO.
En la implementación de actividad principal del Fragmento:
@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://.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);
}
}
Luego, en la implementación de su Fragmento 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);
}
}
Sugiero crear algún tipo de manejo genérico de este problema en su clase de actividad base si tiene acceso a él.
Por ejemplo:
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();
}
}
}
Por supuesto, deberá implementar la interfaz ActivityResultHandler por sus fragmentos y registrarlos en el inicio de la actividad.
Sugiero dos soluciones:
1.) Coloque el IabHelper mHelper en la actividad y llame al IabHelper desde el fragmento.
Algo como:
Para usar esta solución, declare IabHelper como público en la actividad y use un método para llamar al iniciador desde el Fragmento.
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.) En onActivityResult, llame al fragmento apropiado que contiene el objeto IabHelper. El fragmento apropiado puede tener un método de acceso al objeto auxiliar.
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);
}
}
Tienes que llamar
super.onActivityResult(requestCode, resultCode, data);
al comienzo de su Activity''s y Fragment''s onActivityResult para poner en cascada los resultados en los fragmentos.
En mi FragmentActivity esto se lee como
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// No action here, call super to delegate to Fragments
super.onActivityResult(requestCode, resultCode, data);
}
si desea obtener una devolución de llamada en su fragmento que llamar a super.onActivityResult()
desde su actividad.
Esto llamará a tus fragmentos en onActivityResult()
.
Y no olvide llamar a startIntentSenderForResult
desde el contexto de su fragmento.
No use el contexto de actividad getActivity().startIntentSenderForResult
Editar: android.support.v4.app.Fragment
ahora contiene una versión compatible con versiones anteriores de startIntentSenderForResult()
, por lo que esta respuesta es obsoleta.
Respuesta anterior:
A partir de la biblioteca de soporte 23.2.0, la modificación de requestCode ya no funciona: FragmentActivity
ahora realiza un seguimiento de las solicitudes realizadas por sus fragmentos. Agregué este método a FragmentActivity
que hospedaba el Fragment
(código basado en 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);
}
}
Al llamar esto, solo el Fragment
aprobado recibirá la llamada onActivityResult()
.
if (requestCode == RC_REQUEST)
{
Intent intent = new Intent(ContainerAvtivity.this,ContainerAvtivity.class);
startActivity(intent);
finish();
}
RC_REQUEST
es el mismo que RC_REQUEST
para iniciar el flujo de compra
onActivityResult
al onActivityResult
de su Activity. El listener del inventario producirá el resultado deseado para usted (sé que es una solución temporal, pero funcionó para mí).