Implement global Dialog without floating window permissions

In some scenarios, it is necessary to display some prompt pop-up windows, but if you do not grasp the pop-up timing well, the pop-up window will pop up first and then the interface will be killed immediately and the prompt content will not be visible. For example, forced offline: the client returns to the login interface and a prompt pop-up window will pop up.

Author: Abin
Link: https://juejin.cn/post/7295576843653087266

This phenomenon is likely to occur if the activity on the top of the stack is directly used to pop up, without writing the pop-up logic into the specific activity, or if it is difficult to determine the change in activity.

Since applicationContext does not have AppWindowToken, the dialog cannot be created using applicationContext, or windowManager must be used with WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY to create a global floating window. But this approach requires applying for permission. So, how to prevent the dialog from being affected by changes in the activity on the top of the stack without suspending permission?

My idea is to use application.registerActivityLifecycleCallbacks to close the original pop-up window when the activity changes, and recreate the same dialog and display it.

Effect demonstration:

1. The top interface of the stack is killed

67420cc4f8bc005c25b9baeaa05af127.jpeg

2. A new interface pops up

dd604118cda14ea827837a997bdb3d75.jpeg

The following is the code implementation:

/**
 * @Description A global pop-up window that does not require floating permissions. After the activity on the top of the stack changes, it is rebuilt through reflection, so the subclass construction method requires no parameters.
 */
open class BaseAppDialog<T : ViewModel>() : Dialog(topActivity!!.get()!!), ViewModelStoreOwner {


    companion object {
        private val TAG = BaseAppDialog::class.java.simpleName
        private var topActivity: WeakReference<Activity>? = null
        private val staticRestoreList = linkedMapOf<Class<*>, Boolean>() //Second parameter: whether to temporarily close
        private val staticViewModelStore: ViewModelStore = ViewModelStore()


        @JvmStatic
        fun init(application: Application) {
            application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
                override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                    topActivity = WeakReference(activity)
                }


                override fun onActivityStarted(activity: Activity) {


                }


                override fun onActivityResumed(activity: Activity) {
                    topActivity = WeakReference(activity)
                    val tempList = arrayListOf<BaseAppDialog<*>>()
                    val iterator = staticRestoreList.iterator()
                    while (iterator.hasNext()) {
                        val next = iterator.next()
                        val topName = (topActivity?.get() ?: "")::class.java.name
                        if (next.value == true) { //Avoid pop-up windows created by onCreate from popping up repeatedly
                            val newInstance = Class.forName(next.key.name).getConstructor().newInstance() as BaseAppDialog<*>
                            tempList.add(newInstance)
                            Log.e(TAG, "Recreate ${next.key.name} at $topName")
                            iterator.remove()
                        }


                    }


                    tempList.forEach {
                        it.show()
                    }


                    if (staticRestoreList.size == 0) {
                        staticViewModelStore.clear()
                    }
                }


                override fun onActivityPaused(activity: Activity) {
                }


                override fun onActivityStopped(activity: Activity) {


                }


                override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
                }


                override fun onActivityDestroyed(activity: Activity) {
                }
            })
        }
    }




    var vm: T? = null


    init {
        val genericClass = getGenericClass()
        if (vm == null) {
            (genericClass as? Class<T>)?.let {
                vm = ViewModelProvider(this)[it]
            }
        }


        topActivity?.get()?.let {
            (it as LifecycleOwner).lifecycle.addObserver(object : DefaultLifecycleObserver {
                override fun onPause(owner: LifecycleOwner) {
                    super.onPause(owner)
                    dismissSilent()
                }
            })
        }
    }




    // Used to close when the top of the stack changes
    private fun dismissSilent() {
        super.dismiss()
        staticRestoreList.replace(this::class.java, true)
    }


    override fun show() {
        super.show()
        staticRestoreList.put(this::class.java, false)
    }


    override fun dismiss() {
        super.dismiss()
        staticRestoreList.remove(this::class.java)
    }




    //Get the actual type of the generic
    private fun getGenericClass(): Class<*>? {
        val superclass = javaClass.genericSuperclass
        if (superclass is ParameterizedType) {
            val actualTypeArguments: Array<Type>? = superclass.actualTypeArguments
            if (!actualTypeArguments.isNullOrEmpty()) {
                val type: Type = actualTypeArguments[0]
                if (type is Class<*>) {
                    return type
                }
            }
        }
        return ViewModel::class.java
    }




    //Manage the viewModel yourself to restore data
    override fun getViewModelStore(): ViewModelStore {
        return staticViewModelStore
    }
}

If parameters are passed, it is implemented directly by modifying the viewmodel variable of the dialog or calling its method.

class TipDialogVm : ViewModel() {
    val content = MutableLiveData<String>("")
}




class TipDialog2 : BaseAppDialog<TipDialogVm>() {


    var binding : DialogTip2Binding? = null


    init {
        binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.dialog_tip2, null, false)
        binding?.lifecycleOwner = context as? LifecycleOwner
        binding?.vm = vm
        setContentView(binding!!.root)


    }
}

Pop-up window

TipDialog2().apply {
    vm?.content?.value = "Hi hi hi hi"
}.show()

Follow me to get more knowledge or contribute articles

36e3ecf4f867b5e61547113340a5b627.jpeg

82c1bdfac3c9dfabbf6c34d40af41971.jpeg