Skip to content

Instantly share code, notes, and snippets.

@truedem
Last active July 15, 2021 10:47
Show Gist options
  • Save truedem/536aebef8d36edcd4ad902c7b1ff5fd6 to your computer and use it in GitHub Desktop.
Save truedem/536aebef8d36edcd4ad902c7b1ff5fd6 to your computer and use it in GitHub Desktop.
Universal Android Confirmation Dialog
object AppCfg {
const val CONFIRMATION_PARAM = "resultKey"
const val CONFIRMATION_TITLE = "title"
const val CONFIRMATION_TEXT = "content"
const val CONFIRMATION_POSITIVE = "positiveLabel"
const val CONFIRMATION_NEGATIVE = "negativeLabel"
const val CONFIRMATION_POSITIVE_ICON = "positiveIcon"
const val IS_DELETED = "IS_DELETED"
const val IS_EXPORTED = "IS_EXPORTED"
}
class ConfirmationDialog : BaseDialogFragment(), View.OnClickListener {
private val TAG = ConfirmationDialog::class.java.canonicalName
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = requireActivity().layoutInflater.inflate(R.layout.dialog_confirmation, null)
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setView(view)
bindText()
val dialog: Dialog = builder.create()
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
// dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
return dialog
}
private fun bindText() {
with(dialog!!) {
findViewById<TextView>(R.id.titleConfirmation).text = arguments?.getString(AppCfg.CONFIRMATION_TITLE)
findViewById<TextView>(R.id.textConfirmation).let {
it.isVisible = arguments?.getString(AppCfg.CONFIRMATION_TEXT) != null
it.text = arguments?.getString(AppCfg.CONFIRMATION_TEXT)
}
findViewById<MaterialButton>(R.id.buttonConfirmation).let {
it.text = arguments?.getString(AppCfg.CONFIRMATION_POSITIVE) ?: getString(R.string.btn_delete)
it.setOnClickListener(this@ConfirmationDialog)
}
findViewById<MaterialButton>(R.id.buttonCancelConfirmation).let { btn ->
btn.text = arguments?.getString(AppCfg.CONFIRMATION_NEGATIVE) ?: getString(R.string.btn_cancel)
arguments?.getInt(AppCfg.CONFIRMATION_POSITIVE_ICON)?.let {
if (it > 0) btn.setIconResource(it)
}
btn.setOnClickListener(this@ConfirmationDialog)
}
}
}
override fun onClick(v: View?) {
if (BuildConfig.DEBUG) Log.e(TAG, "Sending results back: " + arguments?.getString(AppCfg.CONFIRMATION_PARAM))
sendResult(key = arguments?.getString(AppCfg.CONFIRMATION_PARAM) ?: "", v?.id == R.id.buttonConfirmation)
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/confirmationLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/titleConfirmation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:text="Are you sure?" />
<TextView
android:id="@+id/textConfirmation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:textSize="18sp"
android:text="Are you sure you want to do it?" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal">
<Button
android:id="@+id/buttonCancelConfirmation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:elevation="0dp"
android:layout_marginEnd="16dp"
app:icon="@drawable/ic_close_circle_outline"
android:text="cancel"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon" />
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonConfirmation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:iconGravity="end"
app:icon="@drawable/ic_trash_can_outline"
android:text="confirm" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getResult<Boolean>(AppCfg.IS_DELETED) { deleteItem(it) }
getResult<Boolean>(AppCfg.IS_EXPORTED) { exportItem(it) }
}
fun onDeleteButtonClick() {
navigate(R.id.action_global_confirmationDialog, Bundle().also {
it.putString(AppCfg.CONFIRMATION_PARAM, AppCfg.IS_DELETED)
it.putString(AppCfg.CONFIRMATION_TITLE, "Delete this contanct?")
it.putString(AppCfg.CONFIRMATION_TEXT, "Deleting the contact will also remove their images, history.")
it.putString(AppCfg.CONFIRMATION_POSITIVE, "ok")
it.putString(AppCfg.CONFIRMATION_NEGATIVE, "nah")
it.putInt(AppCfg.CONFIRMATION_POSITIVE_ICON, R.drawable.ic_alert_outlined)
})
}
fun deleteItem(how: Boolean) {
if (how) Log.e("ConfirmedActionDialog", "Confirmed, may proceed")
else Log.e("ConfirmedActionDialog", "Cancelled, forget it")
}
fun exportItem(how: Boolean) {
// see above
}
<action
android:id="@+id/action_global_confirmationDialog"
app:destination="@id/confirmationDialog" />
<dialog
android:id="@+id/confirmationDialog"
android:name="com.example.package.ConfirmationDialog"
android:label="dialog_confirmation"
tools:layout="@layout/dialog_confirmation">
<argument
android:name="resultKey"
app:argType="string" />
<argument
android:name="title"
app:argType="string" />
<argument
android:name="content"
app:argType="string"
app:nullable="true" />
<argument
android:name="positiveLabel"
app:argType="string"
app:nullable="true" />
<argument
android:name="negativeLabel"
app:argType="string"
app:nullable="true" />
<argument
android:name="positiveIcon"
app:argType="integer"
android:defaultValue="0" />
</dialog>
fun <T> Fragment.sendResult(key: String, value: T) {
with(findNavController()) {
previousBackStackEntry?.savedStateHandle?.set(key, value)
// to handle screen rotation, otherwise getResult with ID works
currentBackStackEntry?.savedStateHandle?.set(key, value)
navigateUp()
}
}
// cannot handle screen rotation with opened dialog
fun <T> Fragment.getResult(key: String, observer: Observer<T>) {
val savedStateHandle = findNavController().currentBackStackEntry?.savedStateHandle ?: return
savedStateHandle.getLiveData<T>(key).observe(viewLifecycleOwner, observer)
}
fun <T> DialogFragment.getResult(key: String, observer: Observer<T>) {
val savedStateHandle = findNavController().currentBackStackEntry?.savedStateHandle ?: return
savedStateHandle.getLiveData<T>(key).observe(this, observer)
}
// entry = R.id.myScreen from nav xml map
fun <T> Fragment.getResult(entry: Int, key: String, observer: Observer<T>) {
val savedStateHandle = findNavController().getBackStackEntry(entry).savedStateHandle ?: return
savedStateHandle.getLiveData<T>(key).observe(viewLifecycleOwner, observer)
}
@truedem
Copy link
Author

truedem commented Jul 15, 2021

Notice the click listeners in the dialog - there is a potential for memory leaks. Dispose them on dismiss maybe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment