10

I have implemented Google Pay in app and making paymentData request I am using AutoResolveHelper to display bottom sheet and than get results via onActivityResult. I am making this request from Fragment not from Activity. So I am passing parent activity like this.

 paymentsClient?.loadPaymentData(gpayViewModel.paymentDataRequest)?.let { task ->
            AutoResolveHelper.resolveTask(task, requireActivity(), LOAD_PAYMENT_DATA_REQUEST_CODE)
        }

the problem is that this AutoResolveHelper is not calling onActivityResult on Fragment but only on Activity.

I have read something like this:

If you're calling startActivityForResult() from the fragment then you should call startActivityForResult(), not getActivity().startActivityForResult(), as it will result in fragment onActivityResult().

So it suggest that when AutoResolveHelper is calling startActivityForResult() on passed activity then fragment's onActivityResult will never be called.

So now my only option is to implement onActivityResult in Activity and somehow pass control from this Activity to my child Fragment but this need some boilerplate code and as my Fragment is Reusable than this solution is not perfect.

Meanwhile I have spotted that this code startActivityForResult in correct way and than the Fragment's onActivityResult is called correctly:

 val intent = Intent(activity, CardIOActivity::class.java)
        intent.putExtra(CardIOActivity.EXTRA_REQUIRE_EXPIRY, true)
        intent.putExtra(CardIOActivity.EXTRA_REQUIRE_CVV, true)
        intent.putExtra(CardIOActivity.EXTRA_REQUIRE_CARDHOLDER_NAME, true)

        startActivityForResult(intent, CARD_IO_REQUEST_CODE)

So can I replace this AutoResolveHelper.resolveTask() somehow to execute this task in such way that onActivityResult will not be necessary or I could startActivityForResult myself?

Michał Ziobro
  • 10,759
  • 11
  • 88
  • 143
  • Hi, I'm facing exactly the same issue. Let me know if you find any solution. Thank you. – JerabekJakub Jan 11 '19 at 11:03
  • 1
    We have it quite more complicated since we are using Navigation Architecture Components - we don't have direct access to fragments via TAG or ID. Our code in FragmentActivity: supportFragmentManager.fragments.forEach { fragment -> fragment.childFragmentManager.fragments.forEach { childFragment -> childFragment.onActivityResult(requestCode, resultCode, data) } } (Not really proud of it ;-)) – JerabekJakub Jan 11 '19 at 12:05

3 Answers3

8

As of today, the receipt of a result is bound to the Activity. Part of the reason for that is that the library is not precisely using startActivityForResult to initiate this process; and Fragment support on that same functionality is at the moment limited.

There are basically two ways to circumvent this at the moment (these have been shared in other threads too). However, I personally feel that mixing responsibilities between the fragment and the activity does not provide for great code clarity and clear logic, so as of now, I'd only consider an approach where the activity is responsible for making the call to AutoResolveHelper, capturing the result and sharing it with the fragment. Instead of calling the activity from the fragment, I'd consider doing that through a contract / interface in order to reduce the level of coupling between the two.

A simplistic example could be something like:

interface PaymentsContract {
    fun loadPaymentData(request: PaymentDataRequest, requestCode: Int)
}

Having the activity implement it, and passing it as a reference to the fragment at construction time allows your fragment to stay reusable and agnostic from the activity.

Once the result is ready, you can choose to find the fragment in question and propagate the onActivityResult method to it, or alternatively use a similar contract-based approach for the fragment too.

It'd be useful to learn more about your concrete use cases in order to better understand the rationale to handle this logic within fragments, and ultimately take that feedback back to the team to be considered for future developments.

Hope this is useful.

EDIT: Another alternative to interfaces is to use lambda expressions to let your fragment know about a callable that was defined somewhere else (eg.: in your activity), and that needs to be called when something happens (eg.: when your user hits the "Pay with Google Pay" button).

Jose L Ugia
  • 5,960
  • 3
  • 23
  • 26
1

If you are working with compose, I perfected a nice and clean way to do this which doesn't require any logic in an activity. All logic handled in viewmodel and compose. In short, Google Pay returns a task and that can be handled with ResolvableApiException rather than using AutoResolveHelper. I'll explain how in this post.

First of all, you need to define a launcher in Compose to launch a activity and handle the result. We will use this launcher later in the code.

===YourCompose.kt===
...
    val resolvePaymentResultLauncher =
        rememberLauncherForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result: ActivityResult ->
            when (result.resultCode) {
                RESULT_OK ->
                    result.data?.let { intent ->
                        PaymentData.getFromIntent(intent)?.let {
                            //Do whatever you want
                            viewModel.onGooglePayPaymentDialogSucceeded(it)
                        }
                    }

                RESULT_CANCELED -> {
                    // The user cancelled the payment attempt, basically the pressed X button from the native Google Pay dialog
                }
            }
        }
...//your composables

And in your viewModel you can handle the AutoResolveHandler.

===ViewModelOfYours.kt===
fun onSomethingClicked(onPendingIntentRequested: (PendingIntent) -> Unit){
        val paymentClient : PaymentsClient? = paymentViewModel.getGooglePaymentClient() //Wallet.getPaymentsClient(..) code
        val task = paymentClient?.loadPaymentData(
            PaymentDataRequest.fromJson(
                viewModelState.value.googlePayPayload ?: ""
            )
        )
        task?.addOnCompleteListener { completedTask ->
            when {
                completedTask.isSuccessful -> {
                    //No-op
                }

                completedTask.exception is ResolvableApiException -> {
                    onPendingIntentRequested((completedTask.exception as ResolvableApiException).resolution) // here we are requesting the pending intent, which will be the moment when Google Pay native dialog will be shown
                }

                else -> {
                    //TODO handle exception
                }
            }
        }
    }

Then finally, you need to use this function onSomethingClicked(...) from your composable. The moment of trigger lets say.

====YourComposable.kt===
...
Button(onClick = { 
//remember the launcher we created? So we will launch that here, then it will trigger the activity and result will be handled where we defined `resolvePaymentResultLauncher`.
viewModelOfYours.onSomethingClicked() { pendingIntent ->
 resolvePaymentResultLauncher.launch(IntentSenderRequest.Builder(pendingIntent).build())
}
}) 
{  
//button content
}
...

I hope this helps to clean-up your code from Activity and handle your logic from compose and in your viewmodel. I hope I was able to break it down to small/understandable steps. Please feel free, I'd appreciate the feedback <3

Credit goes to here, I got inspired and perfected the solution @Odin gave. Google pay Api in Jetpack compose - How to

Eren Utku
  • 1,731
  • 1
  • 18
  • 27
-1

Here is a link to the working code. It may be not the perfect approach but it gets the work done. https://readyandroid.wordpress.com/onactivityresult-is-not-being-called-in-fragment/

Jaspal
  • 601
  • 2
  • 13
  • 23