plaid / plaid-link-android

Plaid Link Android SDK
https://plaid.com/docs/link/android
MIT License
114 stars 48 forks source link

[Android 14 Issue] After Authentication Plaid reopen the chrome browser and does not call the success callback #269

Closed aadityapaliwal closed 5 months ago

aadityapaliwal commented 5 months ago

I have integrated Plaid SDK in our live app and it's working till Android 13 devices but with Android 14 device, it's not completing the flow and just redirect on the browser again.

Environment

Android OS Version Android 14 (API level 34)
Android Devices/Emulators Pixel 7 emulator (Tested in real device as well)

Plaid version - implementation 'com.plaid.link:sdk-core:3.6.2' I have also update the Plaid version to 3.12.0 and try but facing same issue.

Expected Result

It should stay on the app and call the onActivityResult with success callback

Recording

https://github.com/plaid/plaid-link-android/assets/92382212/7ba644dd-d7a4-4ecf-8aee-3e4b2330f374

Code To Reproduce Issue


import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.lifecycle.Observer
import com.plaid.link.Plaid
import com.plaid.link.PlaidHandler
import com.plaid.link.linkTokenConfiguration
import com.plaid.link.result.LinkAccount
import com.plaid.link.result.LinkExit
import com.plaid.link.result.LinkResultHandler
import com.plaid.link.result.LinkSuccess
import org.koin.androidx.viewmodel.ext.android.viewModel

class PlaidSetupFragment : BaseFragment<FragmentPlaidSetupBinding>(), ItemSelectionListener<LinkAccount?> {

    private val viewModel: PlaidSetupViewModel by viewModel()

    override fun setBinding(
        inflater: LayoutInflater,
        container: ViewGroup?
    ) = FragmentPlaidSetupBinding.inflate(inflater, container, false)

    override fun setupViewsAndObservers() {
        setupHeader()

        viewModel.linkToken.observe(viewLifecycleOwner, Observer {
            it?.let {
                val linkTokenConfiguration = linkTokenConfiguration {
                    token = it
                }

                val plaidHandler: PlaidHandler = Plaid.create(App.instance, linkTokenConfiguration)
                plaidHandler.open(this)
            }
        })

        startPlaidSetup()

        observeErrorAndLoading(loading = viewModel.loading)

        viewModel.error.observe(viewLifecycleOwner, Observer {
            it?.let {
                showMessage(getString(R.string.error), it, getString(R.string.ok))
            }
        })

        viewModel.onAccountLinked.observe(viewLifecycleOwner, Observer {
            if (it) {
                showMessage("", getString(R.string.account_linked_successfully_dialog_msg), getString(R.string.ok), true)
            }
        })
    }

    private fun showMessage(title: String, message: String, positiveBtnText: String, isAccLinkedCompleted: Boolean = false) {
        DialogHelper.showInfoDialog(requireContext(), title, message, positiveBtnText, object: InfoDialogCallback {
            override fun onPositiveBtnClicked() {

            }

            override fun onDialogDismiss() {
                dismissDialog(isAccLinkedCompleted)
            }
        })
    }

    private fun dismissDialog(isAccLinkedCompleted: Boolean) {
        val bundle = Bundle()
        if (isAccLinkedCompleted) {
            bundle.putBoolean("isLinked", true)
            requireActivity().supportFragmentManager.setFragmentResult(Constants.PLAID_SETUP_REQ_KEY, bundle)
        }
        onBackPressed()
    }

    private fun startPlaidSetup() {
        if (viewModel.linkToken.value.isNullOrEmpty()) {
            viewModel.getLinkToken()
        }
    }

    private fun setupHeader() {
        binding.header1.apply {
            headerName.text = getString(R.string.plaid_setup_screen_title)
            buttonBack.setOnClickListener {
                onBackPressed()
            }
        }
    }

    private val linkResultHandler = LinkResultHandler(
        onSuccess = { linkSuccess: LinkSuccess ->
            viewModel.linkSuccess = linkSuccess
            activity?.supportFragmentManager?.let {
                PlaidAccountsBottomSheetFragment.newInstance(linkSuccess.metadata.accounts, linkSuccess.metadata.institution?.name ?: "", this).show(it, getString(R.string.account_dialog_tag))
            }

        },
        onExit = { it: LinkExit ->
            val message = it.error?.displayMessage ?: (it.error?.errorMessage ?: "")
            if (message.isEmpty()) {
                onBackPressed()
            } else {
                showMessage(getString(R.string.error), message, getString(R.string.ok))
            }
        }
    )

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (!linkResultHandler.onActivityResult(requestCode, resultCode, data)) {
            AppLog.e(MainActivity::class.java.simpleName, "Not handled by the LinkResultHandler");
        }
    }

    override fun onItemSelected(item: LinkAccount?) {
        if (item != null) {
            viewModel.getPermanentAccessToken(item)
        } else {
            showMessage(getString(R.string.error), getString(R.string.account_selection_cancelled_try_again_msg), getString(R.string.ok))
        }
    }

}
aadityapaliwal commented 5 months ago

It's working with Plaid v4.1.1

Closing this issue.