Source code and content mind maps are shown at the end of the article

This day Kong Yiji as usual into starbucks, ordered a large cup of latte, but also skim milk, although on a well-off life, but the problem is still not changed, as usual out of a large number of coins, out of 32.

When I was waiting, I heard a table next to me saying: “Dear brother, don’t touch fish. Today I will treat you to a coffee. This popover function must be added well, and there will be no bugs.” Ha ha, a popover, still can I great Android engineer?” Finger Kong Yiji said: “before he gets the coffee, I can write, the bug does not exist!”

Kong Yiji looked at the lively not too big, go up to, “yo ho, little brother age is not big tone is not big, you can know Android interface using popover, there are four kinds of writing? Let me kong Yiji talk to you today.”

The public smirk: “This Kong Yiji is really Kong Yiji, do you also want to say the character Hui in anise beans?”

Kong Yiji was furious: “This is not the hui character of anise beans! Some of the basics you don’t need today, but what about ancestral code?”

In order to make it clear, we agreed: “Click the button on the interface to trigger the popup window, and text can be entered in the popup window. After confirmation, the input content will be displayed in the text box above the button. To make the scene more realistic, the interface includes the Activity interface and Fragment interface two mainstream cases.

Kong yiji is going to do some preparatory work first: encapsulate two situations to get the Context, bind the View, bind the event

You can ignore the template code below and just skip to the next section for fear of confusionOldest practice: Dialog

sealed class Host {
    abstract val context: Context

    abstract fun <T : View> view(@IdRes id: Int): T
    abstract fun launchFragment(fragment: DialogFragment)
    abstract fun launchFragment2(fragment: DialogFragment, listener: FragmentResultListener)

    class ActivityHost(private val activity: AppCompatActivity) : Host() {
        override val context: Context
            get() = activity

        override fun <T : View> view(id: Int): T {
            return activity.findViewById(id)
        }

        override fun launchFragment(fragment: DialogFragment) {
            // It will unfold below
        }

        override fun launchFragment2(fragment: DialogFragment, listener: FragmentResultListener) {
            // It will unfold below}}class FragmentHost(private val fragment: Fragment) : Host() {
        override val context: Context
            get() = fragment.requireContext()

        override fun <T : View> view(id: Int): T {
            return fragment.requireView().findViewById(id)
        }

        override fun launchFragment(fragment: DialogFragment) {
            // It will unfold below
        }

        override fun launchFragment2(fragment: DialogFragment, listener: FragmentResultListener) {
            // It will unfold below}}}class Demo(val host: Host) {

    interface OnResultListener {
        fun onResult(text: String?).
    }

    companion object {
        const val BUNDLE_STR_INPUT = "BUNDLE_STR_INPUT"
        const val BUNDLE_BOOL_FROM_FG = "BUNDLE_BOOL_FROM_FG"
        const val KEY_INPUT = "KEY_INPUT"
    }

    fun onStart(a) {
        val btnDemo1: Button = host.view(R.id.btn1)
        val btnDemo2: Button = host.view(R.id.btn2)
        val btnDemo3: Button = host.view(R.id.btn3)
        val btnDemo4: Button = host.view(R.id.btn4)

        btnDemo1.setOnClickListener { demo1() }
        btnDemo2.setOnClickListener { demo2() }
        btnDemo3.setOnClickListener { demo3() }
        btnDemo4.setOnClickListener { demo4() }
    }

    fun setText(text: String?). {
        host.view<TextView>(R.id.tv_result).text = text
    }

    private fun demo1(a){}private fun demo2(a){}private fun demo3(a){}private fun demo4(a){}}Copy the code

Kong yiji finished the preparation work easily

Oldest practice: Dialog

If you can still see this, the program must have a long history.

practice

Kong yiji continued: “All you need to do is get the Context and the business callback function.”

private fun demo1(a) {
    val view = LayoutInflater.from(host.context).inflate(R.layout.view_input, null)
    val dialog = AlertDialog.Builder(host.context)
        .setView(view)
        .setPositiveButton("OK") { dialog, _ -> dialog? .dismiss() setText(view.findViewById<EditText>(R.id.et_input)? .text? .toString()) } .setNegativeButton("cancel") { dialog, _ -> dialog? .dismiss() } .create() dialog.show() }Copy the code

advantages

Obviously, the advantages are obvious: simple and easy to encapsulate, you can easily strip out the popover UI to suit your product and UI needs, thus keeping the business part “pure”, efficient reuse, and avoiding changing the business class when the UI changes.

disadvantages

Kong Yiji turned to his younger brother and asked, “Do you know its disadvantages?” Kong Yiji ignored him and said with a smile, “Pop out the popover and turn the screen again. Or open the unreserved activity and go back to the background. Ha ha, is the popover gone?”

Take a closer look. Is there a WindowLeak going on?

This design requires the developer to take extra care of problems caused by life cycle changes.

DialogFragment + setTargetFragment without worrying about life cycle changes

Kong yiji continued: “Google knew this was a bad design, so they added a pop-up interaction system to the Fragment system to solve this problem by taking advantage of the Fragment’s ability to recover from changes in the host lifecycle.”

practice

Kong yiji has done a little more preparation, keeping the function of the Dialog the same as above:

sealed class DemoDialogFragment : DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?).: Dialog {
        val view = LayoutInflater.from(requireContext()).inflate(R.layout.view_input, null)
        return AlertDialog.Builder(requireContext())
            .setView(view)
            .setPositiveButton("OK") { _, _ -> dismissAllowingStateLoss() onPositive(view.findViewById<EditText>(R.id.et_input)? .text? .toString()) } .setNegativeButton("Cancel") { _, _ -> dismissAllowingStateLoss() }
            .create()
    }

    protected abstract fun onPositive(text: String?).
}
Copy the code

Kong Yiji also distinguished the result treatment according to two interface forms:

  • The Fragment source retrieves the targetFragment and the convention calls onActivityResult for a callback
  • The source of the Activity specifies that the Activity must implement the callback interface, and the host Activity must be converted and then called back
class Demo2 : DemoDialogFragment() {
    private var fromFg = false

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState) fromFg = arguments? .getBoolean(BUNDLE_BOOL_FROM_FG) ? :false
    }

    override fun onPositive(text: String?). {
        val intent = Intent()
        intent.putExtras(Bundle().apply {
            putString(BUNDLE_STR_INPUT, text)
        })

        if(fromFg) { targetFragment? .onActivityResult(targetRequestCode, Activity.RESULT_OK, intent) }else {
            (requireActivity() as OnResultListener).onResult(text)
        }
    }
}
Copy the code

Write a “boot” mode

 class ActivityHost(private val activity: AppCompatActivity) : Host() {

    override fun launchFragment(fragment: DialogFragment) {
        valarg = fragment.arguments ? : Bundle() arg.putBoolean(BUNDLE_BOOL_FROM_FG,false)
        fragment.arguments = arg
        fragment.show(activity.supportFragmentManager, fragment.javaClass.name)
    }
}

class FragmentHost(private val fragment: Fragment) : Host() {

    override fun launchFragment(fragment: DialogFragment) {
        valarg = fragment.arguments ? : Bundle() arg.putBoolean(BUNDLE_BOOL_FROM_FG,true)
        fragment.arguments = arg
        fragment.setTargetFragment(this.fragment, 2)
        // Use fragment.requireActivity(). SupportFragmentManager
        fragment.show(this.fragment.requireFragmentManager(), fragment.javaClass.name)
    }
}
Copy the code

Skip the callback processing logic and simply fetch data and method calls

private fun demo2(a) {
    host.launchFragment(DemoDialogFragment.Demo2())
}
Copy the code

advantages

Little brother picked up the demo to play, good good, this no bug.

disadvantages

Kong Yiji is evil evil spirit a smile, you don’t feel this code too TM much? It is very inconvenient to use, there are differences between activities and fragments.

The little brother tentatively replied: “Under the transformation, all dry into a callback function? Isn’t that basically unified?”

Kong Yiji is evil evil spirit a smile, good, this is the third way, you try.

DialogFragment + Callback

Kong Yiji watched his little brother’s fast coding, leaving a Callback in his left hand and an invoke Callback in his right hand

practice

class Demo3 : DemoDialogFragment() {
    var listener: OnResultListener? = null
    override fun onPositive(text: String?).{ listener? .onResult(text) ? : Log.e("DEMO3"."listener is null!")}}private fun demo3(a) {
    host.launchFragment(DemoDialogFragment.Demo3().apply {
        this.listener = object : OnResultListener {
            override fun onResult(text: String?). {
                setText(text)
            }
        }
    })
}
Copy the code

advantages

The younger brother said to Kong Yiji: “Brother Kong, look, it has become very simple and easy to encapsulate again. I don’t worry about changing the business code even if they change the UI. When you look at the life cycle change, it won’t disappear either!”

disadvantages

Kong Yiji evil evil spirit one smile, “little brother don’t happy of too early, turn finished screen measure function return normal not?”

Little brother smell speech a try, big shout: “how did the callback function not! Is there a remedy?”

Kong Yiji said: “Naturally there is, first think about why? Popovers that reappear are restored for you by the system as it handles changes in the host lifecycle, but are not guaranteed to restore all references.”

There are ways to get Callback to be restored, or to get the correct object without having to be restored, but you need to be careful not to leak the host’s memory.

It takes a lot of effort to transform nature, but now it’s a blessing.

Embrace the new change: FragmentResultListener

Kong yiji said: “Google naturally knows many of the problems in method 2 and finally provides a unified solution.”

Update AndroidX-AppCompat to 1.3.0

implementation 'androidx. Appcompat: appcompat: 1.3.0'
Copy the code

We will have new apis:

FragmentManager#setFragmentResultListener(key:String, lifecycleOwner:LifecycleOwner, listener:FragmentResultListener)

FragmentManager#setFragmentResult(key:String, bundle:Bundle)
Copy the code

practice

Kong yiji made the changes using the new API

class ActivityHost(private val activity: AppCompatActivity) : Host() {
    / /...

    override fun launchFragment2(fragment: DialogFragment, listener: FragmentResultListener) {
        activity.supportFragmentManager.setFragmentResultListener(Demo.KEY_INPUT, activity, listener)
        fragment.show(activity.supportFragmentManager, fragment.javaClass.name)
    }
}

class FragmentHost(private val fragment: Fragment) : Host() {
    / /...

    override fun launchFragment2(fragment: DialogFragment, listener: FragmentResultListener) {
        this.fragment.parentFragmentManager.setFragmentResultListener(Demo.KEY_INPUT, this.fragment, listener)
        fragment.show(this.fragment.parentFragmentManager, fragment.javaClass.name)
    }
}
Copy the code

Exhale again after popover

private fun demo4(a) {
    host.launchFragment2(fragment = DemoDialogFragment.Demo4()) { requestKey, result ->
        if(requestKey ! = KEY_INPUT)return@launchFragment2
        setText(result.getString(BUNDLE_STR_INPUT))
    }
}
Copy the code

It’s perfect. At this point, the clerk’s voice floats up: “Mr. Kong Yiji, your large skim milk latte is ready.”

Kong Yiji said to the crowd: “my coffee is good, to go, there is this official scheme, the third method is no longer optimized, as to whether this scheme has shortcomings, you also please read the source code, river’s lake goodbye.”

The attached

Demo code

Content mapping