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()?
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);
}
}
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;
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.
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);
}
}
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.
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.
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);
}
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)).
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.");
}
}
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
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);
}
Related
I have a helper class that takes an activity and does stuff with it.
public MyClass(AppCompatActivity activity, Callbacks callbacks) {
this.activity = activity;
this.callbacks = callbacks;
}
What I do in this class is basically call other activies/libraries in a certain order and use the results. I put this in a helper class so that I can easily reuse the code.
This is the problem I have:
With the new ActivityResultLauncher it's easy to "modularize" what I need:
ActivityResultLauncher<Intent> chooseImageLauncher = activity.registerForActivityResult(...);
...
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
chooseImageLauncher.launch(intent);
And in the implementation of chooseImageLauncher I do whatever I want next. In this case, I will call a library.
The problem is that the library I'm calling was last updated 2020 and doesn't provide a method for an ActivityResultLauncher. It only supports the old way with onActivityResult:
MyLibrary.init().start(activity);
And the result can be caught like this:
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
if (requestCode == MyLibrary.REQUEST_CODE && resultCode == RESULT_OK) {
...
}
}
But I can only implement that in each activity itself. How can I implement here?
You could add a private class inside your helper class that extends Activity. This way you can still catch the value emitted by your library.
private class MyLibraryActivityWrapper extends AppCompatActivity {
public void init() {
MyLibrary.init().start(activity);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == MyLibrary.REQUEST_CODE && resultCode == RESULT_OK) {
}
}
}
I have a problem regarding onActivityResult. First, I opened the Add New Message Fragment to select the image. When I select the image, it doesn't return the result to my current fragment. I put onActivityResult in both my MainActivity and AddMessageFragment but it doesn't call the result in my fragment. My MainActivity is used to set up the Navigation Controller. I use Matisse library for my image picker. Can someone please help me with me? Been stuck for the whole day with this issue.
MainActivity
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
}
AddMessageFragment
#OnClick({R.id.addImage})
public void openGallery()
{
permission.addOnStorageListener(new Permission.OnStorageSuccess()
{
#Override
public void OnStorageSuccess()
{
permission.requestCamera();
}
});
permission.addOnCameraListener(new Permission.OnCameraSuccess()
{
#Override
public void OnCameraSuccess()
{
Matisse.from(getActivity())
.choose(MimeType.of(MimeType.JPEG, MimeType.PNG))
.countable(false)
.capture(true)
.captureStrategy(new CaptureStrategy(true, "com.gprop.users.fileprovider"))
.maxSelectable(1)
.restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
.thumbnailScale(0.85f)
.imageEngine(new Glide4Engine())
.originalEnable(false)
.forResult(99);
}
});
permission.requestStorage();
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 99 && resultCode == Main.RESULT_OK)
{
mSelected = Matisse.obtainPathResult(data);
if(mSelected.size() > 0)
{
Glide.with(this).load(mSelected.get(0)).into(addImage);
Luban.compress(new File(mSelected.get(0)), getActivity().getFilesDir())
.putGear(Luban.THIRD_GEAR)
.asObservable()
.subscribe(new Consumer<File>()
{
#Override
public void accept(File file) throws Exception
{
newfile = file;
}
}, new Consumer<Throwable>()
{
#Override
public void accept(Throwable throwable) throws Exception
{
throwable.printStackTrace();
Toast.makeText(getActivity(), throwable.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
else
{
Toast.makeText(getActivity(), "No image selected", Toast.LENGTH_SHORT).show();
}
}
}
You are calling Matisse.from() on the Activity which you are getting from the getActivity() method, tit must be returning you your MainActivity and the onActivityResult will be called for the MainActivity where you are doing nothing and calling super.onActivityResult().
Possible Solutions: .
Shift your onActivityResult code logic from your AddMessageFragment to MainActivity
Change you Matisse call from Matisse.from(getActivity()) to Matisse.from(this), because looking at the source code of Matisse it also supports Fragment context.
Using the second solution you should get your onActivityResult callback in the fragment and you won't need to change/shift any other code logic.
Hope this helps :)
An explicit call from fragment to the onActivityResult function is as follows.
In the parent Activity class, override the onActivityResult() method and even override the same in the Fragment Class and call as the following code.
In MainActivity class:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.layoutContainer);
fragment.onActivityResult(requestCode, resultCode, data);
}
In AddMessageFragment class:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// In fragment class callback
}
Replace your activity onActivityResult method by below code
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
Fragment fragment = (Fragment) getChildFragmentManager().findFragmentByTag(childTag);
if (fragment != null) {
fragment.onActivityResult(requestCode, resultCode, intent);
}
}
It will call your fragment onActivityResult after this execute.
OnActivityResult() function works on Activity. I suggest you call this function on your activity and implement your logic inside your fragment onResume() function. In order to get data from Activity use global val.
I have an adapter inside a fragment, in this adapter I am running an activityForResult to an activity.
Calling the adapter from the Fragment
mAdapClinicSchedule = new ClinicScheduleAdapter(getContext(),mScheduleList);
mLvClinicsSchedules.setAdapter(mAdapClinicSchedule);
Adapter's constructor:
public ClinicScheduleAdapter(Context context, ArrayList<String> mScheduleList) {
this.context = context;
this.mScheduleList = mScheduleList;
}
The call to the startActivityForResult from the adapter
case R.id.tvDaysClinicSchedule:
Intent intent = new Intent(context, ScheduleDaysActivity.class);
((Activity)context).startActivityForResult(intent, 2);
break;
The setResult from the activity
case R.id.tvSelectDays:
checkDaysSelected();
Intent returnIntent = new Intent();
returnIntent.putExtra("monday", mondaySelected);
returnIntent.putExtra("tuesday", tuesdaySelected);
returnIntent.putExtra("wednesday", wednesdaySelected);
returnIntent.putExtra("thursday", thursdaySelected);
returnIntent.putExtra("friday", fridaySelected);
returnIntent.putExtra("saturday", saturdaySelected);
returnIntent.putExtra("sunday", sundaySelected);
setResult(Activity.RESULT_OK, returnIntent);
finish();
break;
Creating an onActivityResult interface
public interface OnActivityResult {
void onActivityResult(int requestCode, int resultCode, Intent data);
}
That implements the adapter:
public class ClinicScheduleAdapter
extends BaseAdapter implements View.OnClickListener, OnActivityResult { ... }
In the fragment it overrite the onActivityResult
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mAdapClinicSchedule.onActivityResult(requestCode, resultCode, data);
//super.onActivityResult(requestCode, resultCode, data);
}
But in the adapter, it does not show the toast, I do not know what I can be doing wrong
public void onActivityResult (int requestCode, int resultCode, Intent data) {
if (requestCode == 2) {
if (resultCode == Activity.RESULT_OK) {
Toast.makeText(context, "it worked!", Toast.LENGTH_LONG).show();
}
}
}
From the android documentation:
Of course, the activity that responds must be designed to return a result. When it does, it sends the result as another Intent object. Your activity receives it in the onActivityResult() callback.
Although, if I remember correctly, onActivityResult() will get called on whatever started the activity for result. So if you use the activity to start your new intent, then the activity will receive the result call. This means if you want the fragment to handle it, you'll need to call startActivityForResult(...) on the fragment rather than the activity
fragment source
I have activity with one fragment, and then that fragment call some activity which that activity will give some value into first activity. i use onActivityResult but i don't know why resultCode always 0 and data always zero,.
on Activity One i have
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "onActivityResult: "+ requestCode +" "+resultCode+" "+data);
}
Activity one have some fragmentX which call activity two
private final int REQUEST_CODE = 10;
private void start (){
Intent intent = new Intent(getContext(),Main2Activity.class);
intent.putExtra("xxx","test1");
getActivity().startActivityForResult(intent,REQUEST_CODE);
}
then in Activity Two, when i touch onBackpress, will pass some value.
public class Main2Activity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
#Override
public void onBackPressed() {
super.onBackPressed();
Intent intent = getIntent().putExtra("yyy","test2");
setResult(RESULT_OK,intent);
}
}
but, i don't know why, onActivityResult i can't get the data.
my final purpose is, i want to setArgument that data to fragmentX.
1.You can call onActivityResult in the fragment
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "onActivityResult: "+ requestCode +" "+resultCode+" "+data);
}
2.Use super.onBackPressed(); after setResult in your method
#Override
public void onBackPressed() {
Intent intent = getIntent().putExtra("yyy","test2");
setResult(RESULT_OK,intent);
super.onBackPressed();
}
In first activity write the below code:
1st method:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
fragment.onActivityResult(requestCode, resultCode, data);
}
}
In Fragment
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// In fragment write your logic here
}
2nd method: (I didn't try but as per doc may be this is one of the reason try once) if it will not work go 1st method its 100 percent solution
Here when your calling second activity using getContext()
getContext() - Returns the context view only current running activity.
getActivity()- Return the Activity this fragment is currently associated with.
Try instead of getContext() use getActivity() when you calling intent to next activity
Ex:
private final int REQUEST_CODE = 10;
Intent intent = new Intent(getActivity(),Main2Activity.class);
intent.putExtra("xxx","test1");
getActivity().startActivityForResult(intent,REQUEST_CODE);
For start new activity
private final int REQUEST_CODE = 10;
private void start (){
Intent intent = new Intent(getContext(),Main2Activity.class);
intent.putExtra("xxx","test1");
getActivity().startActivityForResult(intent,REQUEST_CODE);
}
From Second activity
public class Main2Activity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
#Override
public void onBackPressed() {
super.onBackPressed();
Intent intent = getIntent().putExtra("yyy","test2");
setResult(10,intent);
}
}
you have to set the integer code using which you have started second activity. you have started second activity with the code "10".so use the same value when you setResult(10,intent);
onActivityResult you need to check Request code
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode==10){
if(data!=null)
{
//you have to manage your own list of fragments for activity because getSupportFragmentManager().getFragments() is deprecated now.
//Send data to fragments
if (arrayFragments != null) {
for (Fragment fragment : arrayFragments) {
fragment.onActivityResult(requestCode, resultCode, data);
}
}
}
}
}
Remember you have to manage your own list of fragments using ArrayList or HashMap.
Hope this will help you if you need any other help inform me.
if you wants to send return data in onActivityResult then you should use super.onBackPressed() after data set in setResult() method
example:
#Override
public void onBackPressed() {
val intent = Intent()
intent.putExtra("IsReturnData", true)
setResult(RESULT_OK,intent);
super.onBackPressed(); //write this line at the end of method
}
I am searching for a solution to get my test (which actually is an ActivityInstrumentationTextCase2) testing the onActivityResults method of my Activity with a special (mock) request- / resultcode and intent...
Code:
method should be tested:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
//In this case the user selected an image from his harddrive
case StartupActivity.PICK_IMAGE:
if (resultCode == Activity.RESULT_OK) {
this.processSelectedFile(data.getData());
}
break;
}
}
test class:
public void testOnActivityResult() {
//here I would like to call the onActivityResult method from my mActivity object.
}
To test a protected member, declare (in your test project) a new class that derives from your target:
public class TestMyActivity : MyActivity
{
//constructors
public void testOnActivityResult(int requestCode, int resultCode, Intent data) {
//call super one
super.onActivityResult(requestCode, resultCode, data);
}
}
Now in your test, create one of these and you can call the method.
Source of idea:
http://codebetter.com/karlseguin/2009/08/19/testing-protected-methods-is-easy/