Update from the next day

In this article, it’s time to throw out onActivityResult when the activity is destroyed and rebuilt. The comments of a silly hair of the reply to understand, and write a demo to try it does exist in this situation.

For example, the activity is rebuilt so that the BTN referenced in the original method is not the BTN in the reconstructed activity.

NoResult.startActivityForResult(activity, intent, requestCode){ _: Int, _ :Int, _: Intent? -> btn? .text ="2002"
}
Copy the code

About startActivityForResult

StartActivityForResult is not used much in normal development, but it is very troublesome to use. You need to rewrite onActivityResult, you need to write callback handling a long way away, and there are plenty of open source libraries that handle this. For several reasons, I decided to write my own tools to deal with this problem.

  1. The solution is not complex and can do the basic functionality on its own. Use kotlin’s basic syntax for low frequency implementations, without the need for chain and Rx extensions.
  2. StartActivityForResult is not used very often, and it is not necessary to introduce an additional third-party library in a project due to minimal requirements
  3. Writing your own initiative is greater

The solution

The solution is similar to RxPermissions’, in that all startActivityForResults are generated by a hidden fragment, and using SparseArray directly to store method references reduces the write interface (kotlin’s convenience). RetainInstance = true keeps the fragment from being destroyed out of the Activity. Fragments will eventually callback methods startActivityForResult activity startActivityFromFragment to ensure that it is in the results will eventually callback to the fragments.

class ResultFragment : Fragment() {

    private val callbacks =
        SparseArray<(requestCode: Int, resultCode: Int.data: Intent?) -> Unit> ()override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        retainInstance = true
    }

    fun startActivityForResult(
        intent: Intent,
        requestCode: Int,
        callback: (requestCode: Int.resultCode: Int.data: Intent?). ->Unit
    ) {
        callbacks.put(requestCode, callback)
        startActivityForResult(intent, requestCode)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int.data: Intent?). {
        callbacks.get(requestCode)? .invoke(requestCode, resultCode,data)
        callbacks.remove(requestCode)
    }
}
Copy the code

Methods are then used to find the fragment

// Find the fragment by tag
private fun findOnResultFragment(activity: FragmentActivity): ResultFragment? =
        activity.supportFragmentManager.findFragmentByTag(tag) as? ResultFragment
// Create a new fragment when no fragment is found
private fun getOnResultFragment(activity: FragmentActivity): ResultFragment {
    returnfindOnResultFragment(activity) ? : activity.supportFragmentManager.run { ResultFragment().apply { beginTransaction().add(this, NoResult.tag).commitAllowingStateLoss()
            executePendingTransactions()
        }
    }
}
Copy the code

Finally, provide the startActivityForResult method

fun startActivityForResult(
        fragment: Fragment, intent: Intent, requestCode: Int,
        callback: (requestCode: Int.resultCode: Int.data: Intent?). ->Unit) {
    startActivityForResult(fragment.requireActivity(), intent, requestCode, callback)
}

fun startActivityForResult(
        activity: FragmentActivity, intent: Intent, requestCode: Int,
        callback: (requestCode: Int.resultCode: Int.data: Intent?). ->Unit) {
    getOnResultFragment(activity).startActivityForResult(intent, requestCode, callback)
}
Copy the code

In practice, this can be used directly. Adjusting the above method to handle requestCode and resultCode can make the method more concise.

NoResult.startActivityForResult(fragment/activity, intent, requestCode){ requestCode: Int, resultCode: Int, intent: Intent? ->
	// The result is...
}
Copy the code

Fragment calling startActivityForResult in ARouter

Query #489 about using Navigation (Activity mContext, int requestCode) in fragments Call the fragment onActivityResult method without calling the fragment onActivityResult method when ARouter calls the startActivityForResult logic in the fragment.

If you use ARouter’s startActivityForResult in your fragment, you will use the startActivityForResult method of the activity that the fragment is attached to. And actual fragments startActivityForResult eventually is to call it startActivityFromFragment attached activity, so it caused the problem.

ARouter roughly works by generating mapping code through annotations and APT tools, and then scanning files to load the mapping cache into SharedPreferences through reflection when the APP is first opened after installation or when it is first opened after an update. The Postcard object is built when the hop is used and then the route jump is performed. The Postcard object contains all the information about this route jump.

Since the Postcard contains all the routing information, process the logic associated with startActivityForResult yourself. In this article, some thoughts triggered by the integration of ARouter have been modified to make relevant implementation. However, as ARouter is public, it can be realized by adding an extension method for it.

// Preprocessing method, implemented in ARouter, but without interceptors and custom callback
internal fun Postcard.pretreatment(context: Context? = null): Postcard? {
    val pretreatmentService = ARouter.getInstance().navigation(PretreatmentService::class.java)
    if (null! = pretreatmentService && ! pretreatmentService.onPretreatment(context,this)) {
        // Pretreatment failed, navigation canceled.
        return null
    }
    try {
        LogisticsCenter.completion(this)}catch (e: Exception) {
        // The router is not found
        / / callback to call
        ARouter.getInstance().navigation(DegradeService::class.java) ? .apply { onLost(context,this@pretreatment)}return null
    }
    // Utility interceptors are not considered
    // Only activities are considered
    if(type ! = RouteType.ACTIVITY)return null
    return this
}
Copy the code

Using Postcards to build an Intent doesn’t need to be too complicated

internal fun Postcard.buildIntent(activity: Activity): Intent {
    return Intent(activity, destination).apply {
        putExtras(this@buildIntent.extras)
        if (this@buildIntent.flags ! = -1)
            flags = this@buildIntent.flags
        if (!this@buildIntent.action.isNullOrEmpty())
            action = this@buildIntent.action
    }
}
Copy the code

Finally, provide the startActivityForResult method, which contains the top implementation

fun Postcard.navigateForResult(
    activity: FragmentActivity, requestCode: Int,
    callback: (requestCode: Int.resultCode: Int.data: Intent?). ->Unit
){ pretreatment(activity)? .apply { NoResult.startActivityForResult(activity, buildIntent(activity), requestCode, callback) } }fun Postcard.navigateForResult(
    fragment: Fragment, requestCode: Int,
    callback: (requestCode: Int.resultCode: Int.data: Intent?). ->Unit
){ pretreatment(fragment.requireContext())? .apply { NoResult.startActivityForResult(fragment, buildIntent(fragment.requireActivity()), requestCode, callback) } }Copy the code

A normal startActivity method is also provided. In ARouter, the direct navigation method jumps to the intent using the Application context, so you have to add FLAG_ACTIVITY_NEW_TASK for the intent. The taskAffinity attribute is used to assign activities to the Activity stack. If there are special requirements, ARouter’s existing jump may not be appropriate, so you can add the following jump to avoid FLAG_ACTIVITY_NEW_TASK. Here’s an article to review later: the launch mode of an Activity.

fun Postcard.navigateActivity(activity: FragmentActivity){ pretreatment(activity)? .apply { activity.startActivity(buildIntent(activity)) } }Copy the code

conclusion

The above simple implementation can basically meet the daily use, if the use of more complex scenarios then you can choose a more complete and easy-to-use tripartite library. Also, different startup modes can have various effects on startActivityForResult, so keep that in mind.