Using AlertDialog
and DialogFragment
The NukeFromOrbit
sample module in the Sampler
and SamplerJ
projects has a UI that consists of a really big button:
Clicking the button brings up the dialog shown earlier in the chapter:
Clicking either button dismisses the dialog, but if you click the “Nuke It” button, we also display a Toast
.
Defining the Dialog
This particular sample uses DialogFragment
and AlertDialog
to display the fragment. The apps have a ConfirmationDialogFragment
that overrides onCreateDialog()
and creates an AlertDialog
to display:
package com.commonsware.jetpack.samplerj.dialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
public class ConfirmationDialogFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final NavController navController = NavHostFragment.findNavController(this);
final ViewModelProvider viewModelProvider = new ViewModelProvider(navController.getViewModelStoreOwner(R.id.nav_graph));
final GraphViewModel vm = viewModelProvider.get(GraphViewModel.class);
return new AlertDialog.Builder(requireActivity())
.setTitle(R.string.dialog_title)
.setMessage(R.string.dialog_message)
.setPositiveButton(R.string.dialog_positive, (dialog, which) -> vm.onAccept())
.setNegativeButton(R.string.dialog_negative, (dialog, which) -> vm.onDecline())
.setOnCancelListener(dialog -> vm.onDecline())
.create();
}
}
package com.commonsware.jetpack.sampler.dialog
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.NavHostFragment
class ConfirmationDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val navController = NavHostFragment.findNavController(this)
val viewModelProvider =
ViewModelProvider(navController.getViewModelStoreOwner(R.id.nav_graph))
val vm = viewModelProvider.get(GraphViewModel::class.java)
return AlertDialog.Builder(requireActivity())
.setTitle(R.string.dialog_title)
.setMessage(R.string.dialog_message)
.setPositiveButton(R.string.dialog_positive) { _, _ -> vm.onAccept() }
.setNegativeButton(R.string.dialog_negative) { _, _ -> vm.onDecline() }
.setOnCancelListener { vm.onDecline() }
.create()
}
}
To create an AlertDialog
, we use an AlertDialog.Builder
. This takes our activity as a constructor parameter, so we use requireActivity()
to get the activity that is hosting this fragment and use it. As the name suggests, AlertDialog.Builder
offers a builder-style API to create the dialog, where we can call a series of functions one after the next. Specifically we configure:
- The title, which is displayed towards the top (“Nuke From Orbit”)
- The message, which is the simplest form of “content” for the dialog, consisting of just a piece of text to display (“Are you sure that you want to nuke it from orbit?”)
- The positive button (“Nuke It”) and negative button (“Um, No”)
- A listener for if the user cancels the dialog by other means, such as pressing the system BACK button
For the buttons, in addition to the caption, we also provide a lambda expression that will be invoked if the button gets clicked. What that lambda expression does, and the rest of the stuff in onCreateDialog()
, we will explore more later on.
Displaying the Dialog
The classic way of displaying a DialogFragment
is to create an instance and call show()
on it. This certainly works and you are welcome to use it.
However, if you are using the Navigation component, you can set up a DialogFragment
as a destination in a navigation graph, simply by using a <dialog>
element instead of a <fragment>
element:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.commonsware.jetpack.sampler.dialog.MainFragment"
android:label="MainFragment" >
<action
android:id="@+id/confirmNuke"
app:destination="@id/confirmationDialogFragment" />
</fragment>
<dialog
android:id="@+id/confirmationDialogFragment"
android:name="com.commonsware.jetpack.sampler.dialog.ConfirmationDialogFragment"
android:label="ConfirmationDialogFragment" />
</navigation>
Here, we have ConfirmationDialogFragment
set up in a graph, reachable by a confirmNuke
action from MainFragment
.
MainFragment
— which happens to be displaying that big button — can then just navigate to ConfirmationDialogFragment
without knowing or caring that this is a dialog versus some other type of destination:
binding.nuke.setOnClickListener(v ->
NavHostFragment.findNavController(MainFragment.this)
.navigate(R.id.confirmNuke));
requireBinding().nuke.setOnClickListener {
NavHostFragment.findNavController(this@MainFragment)
.navigate(R.id.confirmNuke)
}
Here, we are using view binding to bind a click listener to the nuke
button, so when the button is clicked, we show the dialog.
Sharing a ViewModel
Often, an activity or fragment needs a private ViewModel
for managing its own data. However, sometimes, it would be useful to have shared ViewModel
objects. For example, in this sample app, MainFragment
needs to know whether the user clicked the positive or the negative button. This implies some amount of data-sharing between MainFragment
and ConfirmationDialogFragment
, and sharing a ViewModel
would be one approach for that sort of sharing.
That is handled by having the right ViewModelProvider
:
- An activity that creates a
ViewModelProvider
viaViewModelProvider(this)
will getViewModel
objects tied to the activity - A fragment that creates a
ViewModelProvider
viaViewModelProvider(this)
will getViewModel
objects tied to the fragment - A fragment that creates a
ViewModelProvider
by passing the hosting activity to theViewModelProvider
constructor will getViewModel
objects tied to the activity… and if the activity usesViewModelProvider(this)
, the activity and the fragment will share thoseViewModel
objects
In Kotlin, instead of using the viewModels()
property delegate, you can use activityViewModels()
to get a ViewModel
from the activity’s scope, sharing that ViewModel
with the activity.
It is also possible to get a ViewModelProvider
tied not to an activity or a fragment, but to a navigation graph. All of the destinations in that navigation graph will share ViewModel
objects obtained from the graph-specific ViewModelProvider
.
To do that, we:
- Obtain a
NavController
for the fragment, viaNavHostFragment.findNavController()
- Create a
ViewModelProvider
, passing in aViewModelStoreOwner
obtained from theNavController
- Use that
ViewModelProvider
forViewModel
objects to be shared among destinations in the navigation graph
Both MainFragment
and ConfirmationDialogFragment
do this via similar blocks of code:
final NavController navController = NavHostFragment.findNavController(this);
final ViewModelProvider viewModelProvider = new ViewModelProvider(navController.getViewModelStoreOwner(R.id.nav_graph));
final GraphViewModel vm = viewModelProvider.get(GraphViewModel.class);
val navController = NavHostFragment.findNavController(this)
val viewModelProvider =
ViewModelProvider(navController.getViewModelStoreOwner(R.id.nav_graph))
val vm = viewModelProvider.get(GraphViewModel::class.java)
Both our MainFragment
instance and our ConfirmationDialogFragment
instance now share a GraphViewModel
instance.
Reacting to the Dialog
But what we really want is to get the button-click events from ConfirmationDialogFragment
to MainFragment
. The GraphViewModel
is a key piece of that, but we need some other elements as well… and we will take a look at those in an upcoming chapter.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.