Search code examples
androidandroid-architecture-navigation

The best practice to pop up a common error dialog in any destination with navigation component


I want to display an error dialog while receiving errors in any destination fragment's ViewModel.

Now, I created a BaseViewModel and a BaseFragment that does the stuff, and make all fragments and ViewModels extend it.
But it's hard to connect all destinations to the dialog's DialogFragment.

Should I leave the dialog out of the nav_graph.xml?


Solution

  • If you are using kotlin you can do it by using an extension function

    inline fun Activity.alertDialog(
        title: CharSequence? = null,
        message: CharSequence? = null,
        func: AlertDialogHelper.() -> Unit
    ) {
        val dialogFragment = AlertDialogHelper(this, title, message).apply {
            func()
        }
        val fragmentTransaction = (this as AppCompatActivity).supportFragmentManager.beginTransaction()
        fragmentTransaction.let { dialogFragment.show(it, TAG) }
    }
    
    inline fun Fragment.alertDialog(
        title: CharSequence? = null,
        message: CharSequence? = null,
        func: AlertDialogHelper.() -> Unit
    ) {
        val dialogFragment = AlertDialogHelper(this.context!!, title, message).apply {
            func()
        }
        val fragmentTransaction = (this).childFragmentManager.beginTransaction()
        fragmentTransaction.let { dialogFragment.show(it, TAG) }
    }
    

    AlertDialogHelper is the same dialog class that is extended by DialogFragment

    AlertDialogHelper

        class AlertDialogHelper(context: Context, title: CharSequence?, message: CharSequence?) :
        DialogFragment() {
    
        private val dialogView: View by lazyFast {
            LayoutInflater.from(context).inflate(R.layout.dialog_layout, null)
        }
    
        private val title: TextView by lazyFast {
            dialogView.findViewById(R.id.dialogInfoTitleTextView)
        }
    
        private val message: TextView by lazyFast {
            dialogView.findViewById(R.id.dialogInfoMessageTextView)
        }
    
        private val positiveButton: Button by lazyFast {
            dialogView.findViewById(R.id.dialogInfoPositiveButton)
        }
    
        private val negativeButton: Button by lazyFast {
            dialogView.findViewById(R.id.dialogInfoNegativeButton)
        }
    
        var cancelable: Boolean? = true
    
        init {
            this.title.text = title
            this.message.text = message
        }
    
    
        fun positiveButton(text: CharSequence, func: (() -> Unit)? = null) {
            with(positiveButton) {
                this.text = text
                setClickListenerToDialogButton(func)
            }
        }
    
    
        fun negativeButton(text: CharSequence, func: (() -> Unit)? = null) {
            with(negativeButton) {
                this.text = text
                setClickListenerToDialogButton(func)
            }
        }
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            return dialogView
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            title.goneIfTextEmpty()
            message.goneIfTextEmpty()
            positiveButton.goneIfTextEmpty()
            negativeButton.goneIfTextEmpty()
    
            dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
            isCancelable = this.cancelable!!
        }
    
        override fun onDestroyView() {
            super.onDestroyView()
        }
    
        private fun TextView.goneIfTextEmpty() {
            visibility = if (text.isNullOrEmpty()) {
                View.GONE
            } else {
                View.VISIBLE
            }
        }
    
        private fun Button.setClickListenerToDialogButton(func: (() -> Unit)?) {
            setOnClickListener {
                func?.invoke()
                dialog?.dismiss()
            }
        }
    
        fun <T> lazyFast(operation: () -> T): Lazy<T> = lazy(LazyThreadSafetyMode.NONE) {
            operation()
        }
    }