I am using DialogFragments for a number of things: choosing item from list, entering text.
What is the best way to return a value (i.e. a string or an item from a list) back to the calling activity/fragment?
Currently I am making the calling activity implement DismissListener and giving the DialogFragment a reference to the activity. The Dialog then calls the OnDimiss method in the activity and the activity grabs the result from the DialogFragment object. Very messy and it doesn't work on configuration change (orientation change) as the DialogFragment loses the reference to the activity.
Thanks for any help.
Use myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE) from the place where you show the dialog, and then when your dialog is finished, from it you can call getTargetFragment().onActivityResult(getTargetRequestCode(), ...), and implement onActivityResult() in the containing fragment.
It seems like an abuse of onActivityResult(), especially as it doesn't involve activities at all. But I've seen it recommended by official google people, and maybe even in the api demos. I think it's what g/setTargetFragment() were added for.
As you can see here there is a very simple way to do that.
In your DialogFragment add an interface listener like:
public interface EditNameDialogListener {
void onFinishEditDialog(String inputText);
}
Then, add a reference to that listener:
private EditNameDialogListener listener;
This will be used to "activate" the listener method(s), and also to check if the parent Activity/Fragment implements this interface (see below).
In the Activity/FragmentActivity/Fragment that "called" the DialogFragment simply implement this interface.
In your DialogFragment all you need to add at the point where you'd like to dismiss the DialogFragment and return the result is this:
listener.onFinishEditDialog(mEditText.getText().toString());
this.dismiss();
Where mEditText.getText().toString() is what will be passed back to the calling Activity.
Note that if you want to return something else simply change the arguments the listener takes.
Finally, you should check whether the interface was actually implemented by the parent activity/fragment:
#Override
public void onAttach(Context context) {
super.onAttach(context);
// Verify that the host activity implements the callback interface
try {
// Instantiate the EditNameDialogListener so we can send events to the host
listener = (EditNameDialogListener) context;
} catch (ClassCastException e) {
// The activity doesn't implement the interface, throw exception
throw new ClassCastException(context.toString()
+ " must implement EditNameDialogListener");
}
}
This technique is very flexible and allow calling back with the result even if your don;t want to dismiss the dialog just yet.
There is a much simpler way to receive a result from a DialogFragment.
First, in your Activity, Fragment, or FragmentActivity you need to add in the following information:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Stuff to do, dependent on requestCode and resultCode
if(requestCode == 1) { // 1 is an arbitrary number, can be any int
// This is the return result of your DialogFragment
if(resultCode == 1) { // 1 is an arbitrary number, can be any int
// Now do what you need to do after the dialog dismisses.
}
}
}
The requestCode is basically your int label for the DialogFragment you called, I'll show how this works in a second. The resultCode is the code that you send back from the DialogFragment telling your current waiting Activity, Fragment, or FragmentActivity what happened.
The next piece of code to go in is the call to the DialogFragment. An example is here:
DialogFragment dialogFrag = new MyDialogFragment();
// This is the requestCode that you are sending.
dialogFrag.setTargetFragment(this, 1);
// This is the tag, "dialog" being sent.
dialogFrag.show(getFragmentManager(), "dialog");
With these three lines you are declaring your DialogFragment, setting a requestCode (which will call the onActivityResult(...) once the Dialog is dismissed, and you are then showing the dialog. It's that simple.
Now, in your DialogFragment you need to just add one line directly before the dismiss() so that you send a resultCode back to the onActivityResult().
getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();
That's it. Note, the resultCode is defined as int resultCode which I've set to resultCode = 1; in this case.
That's it, you can now send the result of your DialogFragment back to your calling Activity, Fragment, or FragmentActivity.
Also, it looks like this information was posted previously, but there wasn't a sufficient example given so I thought I'd provide more detail.
EDIT 06.24.2016
I apologize for the misleading code above. But you most certainly cannot receive the result back to the activity seeing as the line:
dialogFrag.setTargetFragment(this, 1);
sets a target Fragment and not Activity. So in order to do this you need to use implement an InterfaceCommunicator.
In your DialogFragment set a global variable
public InterfaceCommunicator interfaceCommunicator;
Create a public function to handle it
public interface InterfaceCommunicator {
void sendRequestCode(int code);
}
Then when you're ready to send the code back to the Activity when the DialogFragment is done running, you simply add the line before you dismiss(); your DialogFragment:
interfaceCommunicator.sendRequestCode(1); // the parameter is any int code you choose.
In your activity now you have to do two things, the first is to remove that one line of code that is no longer applicable:
dialogFrag.setTargetFragment(this, 1);
Then implement the interface and you're all done. You can do that by adding the following line to the implements clause at the very top of your class:
public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator
And then #Override the function in the activity,
#Override
public void sendRequestCode(int code) {
// your code here
}
You use this interface method just like you would the onActivityResult() method. Except the interface method is for DialogFragments and the other is for Fragments.
For anyone still reading this: setTargetFragment() has been deprecated. It is now recommended to use the FragmentResultListener API like this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener("requestKey") { key, bundle ->
val result = bundle.getString("resultKey")
// Do something with the result...
}
...
// Somewhere show your dialog
MyDialogFragment.newInstance().show(parentFragmentManager, "tag")
}
Then in your MyDialogFragment set the result:
button.setOnClickListener{
val result = "some string"
setFragmentResult("requestKey", bundleOf("resultKey" to result))
dismiss()
}
Well its too late may be to answer but here is what i did to get results back from the DialogFragment. very similar to #brandon's answer.
Here i am calling DialogFragment from a fragment, just place this code where you are calling your dialog.
FragmentManager fragmentManager = getFragmentManager();
categoryDialog.setTargetFragment(this,1);
categoryDialog.show(fragmentManager, "dialog");
where categoryDialog is my DialogFragment which i want to call and after this in your implementation of dialogfragment place this code where you are setting your data in intent. The value of resultCode is 1 you can set it or use system Defined.
Intent intent = new Intent();
intent.putExtra("listdata", stringData);
getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);
getDialog().dismiss();
now its time to get back to to the calling fragment and implement this method. check for data validity or result success if you want with resultCode and requestCode in if condition.
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//do what ever you want here, and get the result from intent like below
String myData = data.getStringExtra("listdata");
Toast.makeText(getActivity(),data.getStringExtra("listdata"),Toast.LENGTH_SHORT).show();
}
Different approach, to allow a Fragment to communicate up to its Activity:
1) Define a public interface in the fragment and create a variable for it
public OnFragmentInteractionListener mCallback;
public interface OnFragmentInteractionListener {
void onFragmentInteraction(int id);
}
2) Cast the activity to the mCallback variable in the fragment
try {
mCallback = (OnFragmentInteractionListener) getActivity();
} catch (Exception e) {
Log.d(TAG, e.getMessage());
}
3) Implement the listener in your activity
public class MainActivity extends AppCompatActivity implements DFragment.OnFragmentInteractionListener {
//your code here
}
4) Override the OnFragmentInteraction in the activity
#Override
public void onFragmentInteraction(int id) {
Log.d(TAG, "received from fragment: " + id);
}
More info on it: https://developer.android.com/training/basics/fragments/communicating.html
One easy way I found was the following:
Implement this is your dialogFragment,
CallingActivity callingActivity = (CallingActivity) getActivity();
callingActivity.onUserSelectValue("insert selected value here");
dismiss();
And then in the activity that called the Dialog Fragment create the appropriate function as such:
public void onUserSelectValue(String selectedValue) {
// TODO add your implementation.
Toast.makeText(getBaseContext(), ""+ selectedValue, Toast.LENGTH_LONG).show();
}
The Toast is to show that it works. Worked for me.
I'm very surprised to see that no-one has suggested using local broadcasts for DialogFragment to Activity communication! I find it to be so much simpler and cleaner than other suggestions. Essentially, you register for your Activity to listen out for the broadcasts and you send the local broadcasts from your DialogFragment instances. Simple. For a step-by-step guide on how to set it all up, see here.
Or share ViewModel like showed here:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
https://developer.android.com/topic/libraries/architecture/viewmodel#sharing_data_between_fragments
In my case I needed to pass arguments to a targetFragment. But I got exception "Fragment already active". So I declared an Interface in my DialogFragment which parentFragment implemented. When parentFragment started a DialogFragment , it set itself as TargetFragment. Then in DialogFragment I called
((Interface)getTargetFragment()).onSomething(selectedListPosition);
In Kotlin
// My DialogFragment
class FiltroDialogFragment : DialogFragment(), View.OnClickListener {
var listener: InterfaceCommunicator? = null
override fun onAttach(context: Context?) {
super.onAttach(context)
listener = context as InterfaceCommunicator
}
interface InterfaceCommunicator {
fun sendRequest(value: String)
}
override fun onClick(v: View) {
when (v.id) {
R.id.buttonOk -> {
//You can change value
listener?.sendRequest('send data')
dismiss()
}
}
}
}
// My Activity
class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {
override fun sendRequest(value: String) {
// :)
Toast.makeText(this, value, Toast.LENGTH_LONG).show()
}
}
I hope it serves, if you can improve please edit it.
My English is not very good
if you want to send arguments and receive the result from second fragment, you may use Fragment.setArguments to accomplish this task
static class FirstFragment extends Fragment {
final Handler mUIHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 101: // receive the result from SecondFragment
Object result = msg.obj;
// do something according to the result
break;
}
};
};
void onStartSecondFragments() {
Message msg = Message.obtain(mUIHandler, 101, 102, 103, new Object()); // replace Object with a Parcelable if you want to across Save/Restore
// instance
putParcelable(new SecondFragment(), msg).show(getFragmentManager().beginTransaction(), null);
}
}
static class SecondFragment extends DialogFragment {
Message mMsg; // arguments from the caller/FirstFragment
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onViewCreated(view, savedInstanceState);
mMsg = getParcelable(this);
}
void onClickOK() {
mMsg.obj = new Object(); // send the result to the caller/FirstFragment
mMsg.sendToTarget();
}
}
static <T extends Fragment> T putParcelable(T f, Parcelable arg) {
if (f.getArguments() == null) {
f.setArguments(new Bundle());
}
f.getArguments().putParcelable("extra_args", arg);
return f;
}
static <T extends Parcelable> T getParcelable(Fragment f) {
return f.getArguments().getParcelable("extra_args");
}
TL;DR - use this AppDialog class to both pass data into DialogFragment as well as get result out of it.
Detailed explanation:
Premise - Fragments get destroyed and recreated on config changes. View models hang around. When using a Dialog, it is recommended to wrap it in DialogFragment so that when the user rotates device and changes orientation the Dialog will not unexpectedly disappear (the DialogFragment will re-create it and re-display it).
Limitation (hence this question) - The way the DialogFragment works is it takes a class that it will need to re-instantiate on configuration changes - that means one can't have constructor parameters to the subclass to pass parameters, and typically one needs to make custom callbacks through a view model to pass back result of dialog. That typically means a new subclass for every dialog.
The solution - To help with all this, this custom AppDialog fragment comes to the rescue - the parameters are stored in-memory (similar to view model, you can think of it as a tiny custom view model that holds T in memory and uses it to re-create the dialog on config changes) until the dialog fragment is dismissed. The proper way to call back would be through a view model. If the fragment that shows the AppDialog, then you probably already have a view model and you can reference it from the lambda used to create the dialog - that means additional strong reference to the view model until the dialog fragment is dismissed.
Example - see the examples where a simple Dialog is refactored to use this AppDialog utility class to both receive a parameter and do a callback to viewModel to notify of result.
The helper class:
class AppDialog<T>: DialogFragment() {
companion object {
fun<T> buildDialog(params: T? = null, builder: AppDialogLambda<T>): AppDialog<T> {
// Setup arguments
val args = Bundle()
args.putInt("key", pushDialogArgs(params, builder))
// Instantiate
val fragment = AppDialog<T>()
fragment.arguments = args
return fragment
}
// --------------------
// Dialog Arguments
private var lastKey: Int = 0
private val dialogArgs = mutableMapOf<Int, Pair<Any?, AppDialogLambda<*>>>()
private fun pushDialogArgs(params: Any?, builder: AppDialogLambda<*>): Int {
dialogArgs[lastKey] = params to builder
return lastKey++
}
private fun getDialogArgs(key: Int): Pair<Any?, AppDialogLambda<*>> {
return dialogArgs[key]!!
}
private fun deleteDialogArgs(key: Int) {
dialogArgs.remove(key)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Get arguments
val argKey = requireArguments().getInt("key")
val (params, builder) = getDialogArgs(argKey)
// We are getting back our arguments we passed AppDialog.buildDialog and
// the type is guaranteed to be the same. Silence this warning
#Suppress("UNCHECKED_CAST")
return (builder as AppDialogLambda<T>)(this, params as T?)
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
val argKey = requireArguments().getInt("key")
deleteDialogArgs(argKey)
}
}
Example usage (after):
val info = mapOf("message" to "${error.description}\n\nPlease check your Internet connection and try again.")
AppDialog.buildDialog(info) { fragment, params ->
fragment.isCancelable = false // since we are in a DialogFragment
AlertDialog.Builder(fragment.context)
.setTitle("Terms Of Service Failed To Load")
.setMessage(params!!["message"])
.setPositiveButton("Retry") { _, _ ->
// Update the view model instead of calling UserTOSFragment directly
// as the fragment may be destroyed and recreated
// on configuration changes. The viewModel will stay alive.
viewModel.onTermsOfServiceReload()
}
.setNegativeButton("Cancel") { _, _ ->
viewModel.onTermsOfServiceDeclined()
fragment.findNavController().popBackStack()
}.create()
}.show(parentFragmentManager, "TOS Failed Dialog")
Example usage (before):
Without using DialogFragment (for illustration purposes, don't do this, this is bad practice as the dialog will be destroyed on config changes), code inside UserTOSFragment.kt - note code used to call directly the UserTOSFragment.loadContent() on retry. This has to be rewritten to instead call viewModel.onTermsOfServiceDeclined() in the above example:
AlertDialog.Builder(context)
.setTitle("Terms Of Service Failed To Load")
.setMessage("${error.description}\n\nPlease check your Internet connection and try again.")
.setPositiveButton("Retry") { _, _ ->
loadContent()
}
.setCancelable(false)
.setNegativeButton("Cancel") { _, _ ->
viewModel.onTermsOfServiceDeclined()
findNavController().popBackStack()
}
.show()
On a dialog Fragment
class AbcDialogFragment(private val ondata: (data: String) -> Unit) : DialogFragment() {}
Code to show the dialog from fragment/Activity
val abcDialogFragment = AbcDialogFragment(ondata = {data-> })
abcDialogFragment.show(requireActivity().supportFragmentManager, "TAG")
and in the dialog fragment, you can invoke the onData when dialog fragment is closed or any click listeners.
Just to have it as one of the options (since no one mentioned it yet) - you could use an event bus like Otto.
So in the dialog you do:
bus.post(new AnswerAvailableEvent(42));
And have your caller (Activity or Fragment) subscribe to it:
#Subscribe public void answerAvailable(AnswerAvailableEvent event) {
// TODO: React to the event somehow!
}
Related
I have a base Activity in which I have 2 fragments (lets say Fragment1 & Fragment2)
I need to call Fragment2 from Fragment1 for capturing data and then use the same in Fragment1 .
I know the that call of Activity for result can be fetched inside onActivityResult of baseActivity but how will I call the Fragment2 from Fragment1 for result purpose ?
Without MVVM
The simplest built in way i guess is using Fragment.setTargetFragment(#Nullable Fragment fragment, int requestCode)
from fragment 1:
val fragment = Fragment2.newInstane()
fragment.setTargetFragment(this, 10)
from fragment 2 when you wanna get result back tofragment 1:
fun setResultBack(){
val intent = Intent()
// inject anything to intent here
getTargetFragment().onActivityResult(10, Activity.RESULT_OK, intent)
fragmentManager.popBackStack()
}
finally override onActivityResult from fragment 1:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if(requestCode == 10){
// handle result here
}
else super.onActivityResult(requestCode, resultCode, data)
}
And this works in all cases even after restoring fragment state or after configuration change.
with MVVM
Use a shared view model as recommanded from documentation
I think you will get the hint from this solution. Because I cant write code for your requirement, but this will give you an idea, what you can do.
public class Fragment1 extends android.support.v4.app.Fragment {
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof ActivityHome) {
((ActivityHome) context).setFragment1(this);
}
}
}
public class ActivityHome extends AppCompatActivity {
Fragment1 fragment1;
public void setFragment1(Fragment1 fragment1) {
this.fragment1 = fragment1;
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (fragment1 != null) fragment1.onActivityResult(requestCode, resultCode, data);
}
}
Even you can implement this by using interfaces, but I'll suggest you EventBus as this is tiny library, which makes communication very easy.
(1) Make Data Class
public static class MessageEvent { /* Additional fields if needed */ }
(2) Subscribe for Event
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
(3) Register & Unregister
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
and Trigger from anywhere
EventBus.getDefault().post(new MessageEvent());
I'd recommend looking at this tight coupling and refactoring so the fragments aren't so tightly coupled and the data fetch is similarly decoupled. Have a look at the various resources on MVP, MVC, or MVVP for the concept.
Sometimes, though, you do need to have one fragment's info conveyed to another. The typical/recommended way of doing this is to define a "fragment callback" interface that your fragments require and that interface is implemented by your Activity. So the Activity is the medium for communication (which helps with decoupling the fragments.) You can use a custom base class for your fragments so they have a common definition of the interface.
public class MyBaseFragment extends Fragment {
public interface FragmentCallbacks {
// Use whatever arguments you may require here
void fetchData();
}
protected FragmentCallbacks mCallbacks;
#Override
void onAttach(Context context) {
try {
mCallbacks = (FragmentCallbacks)context;
} catch (ClassCastException e) {
throw new RuntimeException("Activity must implement FragmentCallbacks");
}
}
}
Now in Fragment1 you can call mCallbacks.fetchData() which the Activity can use to call into a public method you create in Fragment2. When it gets the data back (probably async), it can then provide it to Fragment1 via a public method you create.
I have Activity in my app with few fragments in it. One of these fragments has a DialogFragment, it called by button click. DialogFragment has 3 buttons - positive, negative and neutral.
public class CompanyNotConnectedToSRDialog extends DialogFragment {
public static final String TAG = CompanyNotConnectedToSRDialog.class.getSimpleName();
private NotConnectedDialogListener mNotConnectedDialogListener;
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity(), R.style.DefaultAlertDialogTheme)
.setTitle("Register")
.setMessage("Do you really want to register?")
.setNeutralButton("Skip", (dialog1, which) -> {
mNotConnectedDialogListener.onSkipBtnNotConnectedDialogPressed();
})
.setNegativeButton("Cancel", null)
.setPositiveButton("Register", (dialog12, which) -> {
mNotConnectedDialogListener.onSendBtnNotConnectedDialogPressed();
})
.create();
}
public interface NotConnectedDialogListener {
void onSkipBtnNotConnectedDialogPressed();
void onSendBtnNotConnectedDialogPressed();
}
public void setListener(NotConnectedDialogListener listener) {
this.mNotConnectedDialogListener = listener;
}
As you can see I created public interface that contains two methods for my skip and register buttons (cancel button listener is null so it doesn't matter) and the Setter for this listener.
Then I implemented this interface in my fragment that calls this dialogFragment, I Overrided methods and called dialogFragment like this:
if (mNotConnectedDialog == null) {
mNotConnectedDialog = new CompanyNotConnectedToSRDialog();
mNotConnectedDialog.setListener(this);
mNotConnectedDialog.show(getActivity().getFragmentManager(), CompanyNotConnectedToSRDialog.TAG);
} else {
mNotConnectedDialog.show(mActivity.getFragmentManager(), CompanyNotConnectedToSRDialog.TAG);
mNotConnectedDialog.setListener(this);
}
The problem is I get NullPointerException if I press the button in my parent Fragment to show DialogFragment, rotate screen and press any button in my DialogFragment, because my listener is null.
java.lang.NullPointerException: Attempt to invoke interface method 'void com.myapp.ui.object.create.dialogs.CompanyNotConnectedToSRDialog$NotConnectedDialogListener.onSendBtnNotConnectedDialogPressed()' on a null object reference
at com.myapp.ui.object.create.dialogs.CompanyNotConnectedToSRDialog.lambda$onCreateDialog$1(CompanyNotConnectedToSRDialog.java:31)
How to handle these clicks and set listeners if this solution is wrong?
PS: please don't tell me about android:configChanges.
So the current solution doesn't work because when you rotate the dialog fragment is destroyed and recreated. So setListener isn't called. The solution depends on if your listener is an activity or another fragment.
If it's an activity you can override onAttach in your DialogFragment and set the listener there. If your listener is a fragment then in your OnCreateDialog method you can look the fragment up by the tag and set the listener that way. For example.
Fragment listenerFragment = getActivity().getSupportFragmentManager().findFragmentByTag(getString(R.string.your_listener_fragment_tag));
if( listenerFragment instanceOf NotConnectedDialogListener ) {
listener = (NotConnectedDialogListener) listenerFragment;
} else {
//Handle what to do if you don't have a listener here. Maybe dismiss the dialog.
}
Yes, after orientation change your listener is null. It's easiest to do the callback to the activity:
public static class DialogFragmentA extends DialogFragment {
// Container Activity must implement this interface
public interface NotConnectedDialogListener {
public void onX();
}
NotConnectedDialogListener mListener;
...
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (NotConnectedDialogListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement NotConnectedDialogListener");
}
}
...
}
Now you can call mListener.onX etc. in your dialog anywhere, also after orientation change. Your container Activity must implement the interface and will receive the method call.
https://developer.android.com/guide/components/fragments.html#EventCallbacks
Here is what I would like to do:
1) Inside an Activity a dialog is shown. I use DialogFragment and FragmentManager for this, by calling:
dialogFragment.show(fragmentManager, "edit_task_list");
2) Inside the Dialog I have layout with a custom Button. I would like to perform some action when the button is clicked and later close the dialog.
How should I connect everything? I see two options:
1) onclick attribute in the Button and a method inside the Actvity. That was my original plan, but I don't how to get the Dialog from the Activity to dismiss it. Even if this is not the right way, how could this be done? I would like to understand how this works.
2) set on click listener on the button when the Dialog is created in DialogFragment. This will require me to pass some context from the Activity to the DialogFragment, so I would like to avoid it (and keep the DialogFragment as simple as possible).
Which of those options should I take?
Number 2 Doesn't require you to pass any context (and you shouldn't). You define an interface that can act as a contract between fragments and activities and make your activity implement it.
From your dialog and in your button.onClick(), you do something like this (untested code):
if ( getActivity() != null
&& !getActivity().finishing()
&& getActivity() instanceOf YourInterface) {
((YourInterface)getActivity()).onSomeNiceMethod();
dismiss(); // close the dialog (if this is what you want).
}
The interface looks like:
public interface YourInterface {
void onSomeNiceMethod();
}
And your Activity…
public class YourActivity implements YourInterface {
void onSomeNiceMethod() {
// Hey! The Button In The Dialog Has Been Pressed!
}
}
All Activity and Fragment classes have a built-in callback method for you to use when you start another Activity, Fragment, Dialog, or DialogFragment.
void onActivityResult(int requestCode, int resultCode, Intent data)
Since you want to start the Dialog from an Activity, using the Dialog class is better than the DialogFragment class. The latter is better for starting a dialog from a Fragment, because it has two methods for communicating back to the Fragment (get/set TargetFragment())
The Dialog class has the getOwnerActivity() method. This is the Activity you use when creating the Dialog with one of its constructors.
You set a onClickListener on the button in the Dialog class. To pass the result back to the Activity:
getOwnerActivity().onActivityResult(intIdentifyingme, Activity.RESULT_OK,
intent);
dismiss(); // close the dialog
You put additional info you want to send in an Intent.
1) onclick attribute in the Button and a method inside the Actvity.
That was my original plan, but I don't how to get the Dialog from the
Activity to dismiss it. Even if this is not the right way, how could
this be done? I would like to understand how this works.
Basically your Activity has to remember/know which dialog is active at the moment with something like curDialog=dialogFragment;, then when handling the button onclick action you'll know which dialog to dismiss. But this is really not a good idea since basically the Button View would "leak" from your DialogFragment to your Activity, which breaks object encapsulation.
2) set on click listener on the button when the Dialog is created in
DialogFragment. This will require me to pass some context from the
Activity to the DialogFragment, so I would like to avoid it (and keep
the DialogFragment as simple as possible).
As a previous answer mentioned, you don't need to pass any Context to it, especially since you can get the Activity by calling getActivity().
The solution depends on whether or not this dialog would be used by multiple Activities:
Used by a single Activity: #Martin's solution will work just fine
Used by multiple Activity: abstraction can be used such that only the user's decision is passed to a listener. This is a (modified) solution I came up for the same problem:
public class BaseDialogFragment extends DialogFragment {
protected TextView dialogEn;
protected Button dialogYes;
private Button dialogNo;
protected OnSelectListener listener;
public interface OnSelectListener {
public void onSelect(int type, boolean yes);
}
public void setOnSelectListener(OnSelectListener listener) {
this.listener = listener;
}
public BaseDialogFragment() {
super();
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.dialog_confirm, container, false);
dialogYes = (Button) v.findViewById(R.id.yes);
dialogNo = (Button) v.findViewById(R.id.no);
dialogEn = (TextView) view.findViewById(R.id.dialog_en);
dialogEn.setText(getArguments().getString("text_en"));
dialogYes.setOnClickListener(this);
dialogNo.setOnClickListener(this);
return v;
}
public void onClick(View v) {
if (listener != null) {
listener.onSelect(getArguments().getInt("type"),
v == dialogYes ? true : false);
}
getDialog().dismiss();
}
}
To use it some additional info needs to be provided:
Bundle bundle = new Bundle();
bundle.putInt("type", type); //type: an unique integer value that helps differentiate result from different dialogs
bundle.putString("text_en", en); //en: String to be displayed
dialog.setArguments(bundle);
dialog.setOnSelectListener(this);
So if the type value above is set to 115, then a dialogYes button click would trigger public void onSelect(int type, boolean yes) method to be called with 115 and true as the 1st & 2nd parameters.
Your first point about the onClick attribute in the xml should be avoided. Because handling a Dialog that way could be really painfull if you respect events like screen rotation or a setup with multiple dialogs. This leads into leaked window errors most of the time and needs unnecessary code overhead to avoid this. Because you have to keep track of the Dialog which is actually shown yourself.
To be able to dismiss the Dialog this way you can use the Tag you setted as you called dialogFragment.show(fragmentManager, "edit_task_list");
DialogFragment frag = (DialogFragment)getFragmentManager().findFragmentByTag("edit_task_list");
if(frag != null)
frag.dismiss();
The proper solution is to use an interface as a callback for the communication between the DialogFragment and the Activity. This keeps the Dialog modular and the code easy. Here is an example from the docs. For this you don't need a Context. You simply pass the interface to the dialog in the onAttach() callback. It has a reference of the Activity as a parameter, which called that Dialog.
// Example interface for the communication
public interface OnArticleSelectedListener {
public void onButtonClicked(/*any Parameters*/);
}
public static class FragmentA extends DialogFragment {
OnArticleSelectedListener mListener;
...
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity; // get the interface of the Activity
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnArticleSelectedListener");
}
}
...
}
Handle the Button click in the Dialog and call dismiss() in it, that the Dialog can dismiss itself. Have a look at this question why to use dismiss() instead of getDialog().dismiss().
yourButton.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v){
if(mListener != null) // check if the listener is still valid
mListener.onButtonClicked(...); // calls the Activity implementation of this callback
dismiss(); // dismiss the Dialog
}
});
In onPause() of the Dialog set the reference of the interface to null. This way you can be sure that the callback will only be used if the Dialog is showing.
Your Activity looks something like this to be able to handle the callback:
public class MyActivity extends Activity implements OnArticleSelectedListener{
...
#Override
public void onButtonClicked(...){
// your implementation here
}
}
I don't know your overall setup but if you would use an AlertDialog a click on the Buttons dismiss the Dialog automatically when the method returns.
Imagine, I have FragmentA from which I startDialogFragment (there are EditText in box). How to can I get back the value from the EditText to FragmentA? I try to make something like this, and this but I was not successful.
The Fragment.onActivityResult() method is useful in this situation. It takes getTargetRequestCode(), which is a code you set up between fragments so they can be identified. In addition, it takes a request code, normally just 0 if the code worked well, and then an Intent, which you can attach a string too, like so
Intent intent = new Intent();
intent.putExtra("STRING_RESULT", str);
Also, the setTargetFragment(Fragment, requestCode) should be used in the fragment that the result is being sent from to identify it. Overall, you would have code in the requesting fragment that looks like this:
FragmentManager fm = getActivity().getSupportFragmentManager();
DialogFragment dialogFragment = new DialogFragment();
dialogFragment.setTargetFragment(this, REQUEST_CODE);
dialogFragment.show();
The class to send data (the DialogFragment) would use this Fragment we just defined to send the data:
private void sendResult(int REQUEST_CODE) {
Intent intent = new Intent();
intent.putStringExtra(EDIT_TEXT_BUNDLE_KEY, editTextString);
getTargetFragment().onActivityResult(
getTargetRequestCode(), REQUEST_CODE, intent);
}
To receive the data, we use this type of class in the Fragment which initially started the DialogFragment:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Make sure fragment codes match up
if (requestCode == DialogFragment.REQUEST_CODE) {
String editTextString = data.getStringExtra(
DialogFragment.EDIT_TEXT_BUNDLE_KEY);
At this point, you have the string from your EditText from the DialogFragment in the parent fragment. Just use the sendResult(int) method in your TextChangeListener() anonymous class so that the text is sent when you need it.
Assume a situation that you are uploading some file to server , on clicking of upload button a dialog should open,prompting for title and optional tag.And the dialog itself containing 2 buttons say cancel and continue.
make the UI as you wish by using layout xml file.
then create one class that extending DialogFragment. inflate the layout and initialize views inside onCreateView() method.
Inside that class create one interface
public interface uploadDialogInterface
{
public void senddata(String title, String tag);
}
uploadDialogInterface interfaceObj;
String title="";
String tag=" ";
And the important thing is you need to override onAttach() method
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.context=context;
interfaceObj= (uploadDialogInterface) getTargetFragment();
}
And in the on Button click call the interface method like
#Override
public void onClick(View v) {
int id=v.getId();
if(id== R.id.vB_fud_cancel)
{
dismiss();
}
else if(id== R.id.vB_fud_upload)
{
title=mVideotitle.getText().toString();
tag=mOptionaltag.getText().toString();
if(mVideotitle.getText().toString().isEmpty()) {
Snackbar.make(mVideotitle,"Please enter the video title", Snackbar.LENGTH_SHORT).show();
}else
{
interfaceObj.senddata(title,tag);
dismiss();
}
}
}
And inside the Fragment or activity from which you are launching the dialog should contain setTargetFragment attribute.
private void callUploadDialog()
{
UploadDialogFragment fragment = new UploadDialogFragment();
fragment.setTargetFragment(this, 0);
FragmentManager manager = getFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.setCustomAnimations(R.anim.fade_in, R.anim.fade_in);
fragment.show(ft, "UploadDialogFragment");
fragment.setCancelable(false);
}
And finally you should implement the interface (that was declared inside the dialog fragment) and override the method
#Override
public void senddata(String title,String optionaltag) {
this.videoTitle=title;
this.optionalTag=optionaltag;
}
I think this post will be helpful for those who are using dialog fragment for the first time . I was struggled to find the solution . And hopefully this will solve someone's problem in the future.
(Sorry for the language)
One of the better and simpler ways to do this is using Android ViewModel.
This helps in easier sharing of data, without the need of sending any data across fragments. You could do this not only for DialogFragments, but also for normal Fragments.
Source: https://developer.android.com/topic/libraries/architecture/viewmodel
Here is what I did
My ViewModel looks as below
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
public class PlayerViewModel extends ViewModel {
private final MutableLiveData<Player> selectedPlayer = new MutableLiveData<>();
public LiveData<Player> getSelectedPlayer() {
return selectedPlayer;
}
public void selectPlayer(Player player) {
selectedPlayer.setValue(player);
}
}
In the Fragment where I select a Player, I use the following code in the onCreate method to bind the ViewModel
playerViewModel = ViewModelProviders.of(getActivity()).get(PlayerViewModel.class);
When a specific Player is selected, use the following (You can use an ArrayAdapter, DialogFragment's selector or anything you want to display list of players)
playerViewModel = ViewModelProviders.of(getActivity()).get(PlayerViewModel.class);
And finally, in the fragment where you need to show the Player information, do the following in the onCreate method
PlayerViewModel model = ViewModelProviders.of(getActivity()).get(PlayerViewModel.class);
model.getSelectedPlayer().observe(this, new Observer<Player>() {
#Override
public void onChanged(#Nullable Player selPlayer) {
if (selPlayer != null)
player = selPlayer;
populateData();
}
});
You need to send the data from the dialog back to the activity via a callback method, then have the activity give that data back to the fragment you want it to go to. Just a quick example:
public void datFromDialog(String data){
MyFragment mf = (MyFragment)getFragmentManager().findFragmentById(r.id.frag);
mf.iWantNewData(data);
}
What you want, according to Android Developers...
This method ensures that the calling fragment implements the onChangeListener of the dialog.
FragmentA (calling fragment):
MyDialogFragment f = new MyDialogFragment();
Bundle args = new Bundle();
args.putString("data", data);
f.setArguments(args);
// Set the calling fragment for this dialog.
f.setTargetFragment(FragmentA.this, 0);
f.show(getActivity().getSupportFragmentManager(), "MyDialogFragment");
MyDialogFragment:
import android.support.v4.app.DialogFragment;
public class MyDialogFragment extends DialogFragment {
public OnChangeListener onChangeListener;
interface OnChangeListener{
void onChange(Data data);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get the calling fragment and ensure that it implements onChangeListener.
try {
onChangeListener = (OnChangeListener) getTargetFragment();
} catch (ClassCastException e) {
throw new ClassCastException(
"The calling Fragment must implement MyDialogFragment.onChangeListener");
}
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
.....
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
// Send the data to the calling fragment.
onChangeListener.onChange(data);
}
});
.....
}
}
dialogFragment.setTargetFragment is deprecated, see : doc
Instead of using a target fragment to pass results, the fragment requesting a result should use FragmentManager.setFragmentResultListener(String, LifecycleOwner, FragmentResultListener) to register a FragmentResultListener with a requestKey using its parent fragment manager. The fragment delivering a result should then call FragmentManager.setFragmentResult(String, Bundle) using the same requestKey. Consider using setArguments to pass the requestKey if you need to support dynamic request keys.
Here is a simple implementation :
Call from host Fragment
val dialog = MockDialog.newInstance(
"requestKey")
dialog.show(
childFragmentManager, MockDialog.TAG
)
In MockDialog (which extends DialogFragment):
dialog.setPositiveButton(R.string.dialog_yes) { _, _ ->
parentFragmentManager.setFragmentResult(
arguments!!.getString(DIALOG_REQUEST_PARAM)!!,// which is "requestKey"
//add data to bundle
bundleOf("result" to "any data")
)
}
Get result on host Fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
childFragmentManager.setFragmentResultListener(
"requestKey", this
) { requestKey, result ->
// you data here
val data = result.getString("result", null)
}
}
Base-line : you need to pass your "requestKey" and pass it back to host-fragment
Good luck,'.
I am trying to prevent dialogs built with Alert builder from being dismissed when the Activity is restarted.
If I overload the onConfigurationChanged method I can successfully do this and reset the layout to correct orientation but I lose sticky text feature of edittext. So in solving the dialog problem I have created this edittext problem.
If I save the strings from the edittext and reassign them in the onCofiguration change they still seem to default to initial value not what was entered before rotation. Even if I force an invalidate does seem to update them.
I really need to solve either the dialog problem or the edittext problem.
Thanks for the help.
The best way to avoid this problem nowadays is by using a DialogFragment.
Create a new class which extends DialogFragment. Override onCreateDialog and return your old Dialog or an AlertDialog.
Then you can show it with DialogFragment.show(fragmentManager, tag).
Here's an example with a Listener:
public class MyDialogFragment extends DialogFragment {
public interface YesNoListener {
void onYes();
void onNo();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof YesNoListener)) {
throw new ClassCastException(activity.toString() + " must implement YesNoListener");
}
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.dialog_my_title)
.setMessage(R.string.dialog_my_message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((YesNoListener) getActivity()).onYes();
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((YesNoListener) getActivity()).onNo();
}
})
.create();
}
}
And in the Activity you call:
new MyDialogFragment().show(getSupportFragmentManager(), "tag"); // or getFragmentManager() in API 11+
This answer helps explain these other three questions (and their answers):
Android Best way of avoid Dialogs to dismiss after a device rotation
Android DialogFragment vs Dialog
How can I show a DialogFragment using compatibility package?
// Prevent dialog dismiss when orientation changes
private static void doKeepDialog(Dialog dialog){
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.copyFrom(dialog.getWindow().getAttributes());
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
dialog.getWindow().setAttributes(lp);
}
public static void doLogout(final Context context){
final AlertDialog dialog = new AlertDialog.Builder(context)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.titlelogout)
.setMessage(R.string.logoutconfirm)
.setPositiveButton("Yes", new DialogInterface.OnClickListener()
{
#Override
public void onClick(DialogInterface dialog, int which) {
...
}
})
.setNegativeButton("No", null)
.show();
doKeepDialog(dialog);
}
If you're changing the layout on orientation change I wouldn't put android:configChanges="orientation" in your manifest because you're recreating the views anyway.
Save the current state of your activity (like text entered, shown dialog, data displayed etc.) using these methods:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
That way the activity goes through onCreate again and afterwards calls the onRestoreInstanceState method where you can set your EditText value again.
If you want to store more complex Objects you can use
#Override
public Object onRetainNonConfigurationInstance() {
}
Here you can store any object and in onCreate you just have to call getLastNonConfigurationInstance(); to get the Object.
Just add android:configChanges="orientation" with your activity
element in AndroidManifest.xml
Example:
<activity
android:name=".YourActivity"
android:configChanges="orientation"
android:label="#string/app_name"></activity>
A very easy approach is to create the dialogs from the method onCreateDialog() (see note below). You show them through showDialog(). This way, Android handles the rotation for you and you do not have to call dismiss() in onPause() to avoid a WindowLeak and then you neither have to restore the dialog. From the docs:
Show a dialog managed by this activity. A call to onCreateDialog(int, Bundle) will be made with the same id the first time this is called for a given id. From thereafter, the dialog will be automatically saved and restored.
See Android docs showDialog() for more info. Hope it helps somebody!
Note: If using AlertDialog.Builder, do not call show() from onCreateDialog(), call create() instead. If using ProgressDialog, just create the object, set the parameters you need and return it. In conclusion, show() inside onCreateDialog() causes problems, just create de Dialog instance and return it. This should work! (I have experienced issues using showDialog() from onCreate() -actually not showing the dialog-, but if you use it in onResume() or in a listener callback it works well).
This question was answered a long time ago.
Yet this is non-hacky and simple solution I use for myself.
I did this helper class for myself, so you can use it in your application too.
Usage is:
PersistentDialogFragment.newInstance(
getBaseContext(),
RC_REQUEST_CODE,
R.string.message_text,
R.string.positive_btn_text,
R.string.negative_btn_text)
.show(getSupportFragmentManager(), PersistentDialogFragment.TAG);
Or
PersistentDialogFragment.newInstance(
getBaseContext(),
RC_EXPLAIN_LOCATION,
"Dialog title",
"Dialog Message",
"Positive Button",
"Negative Button",
false)
.show(getSupportFragmentManager(), PersistentDialogFragment.TAG);
public class ExampleActivity extends Activity implements PersistentDialogListener{
#Override
void onDialogPositiveClicked(int requestCode) {
switch(requestCode) {
case RC_REQUEST_CODE:
break;
}
}
#Override
void onDialogNegativeClicked(int requestCode) {
switch(requestCode) {
case RC_REQUEST_CODE:
break;
}
}
}
Definitely, the best approach is by using DialogFragment.
Here is mine solution of wrapper class that helps to prevent different dialogs from being dismissed within one Fragment (or Activity with small refactoring). Also, it helps to avoid massive code refactoring if for some reasons there are a lot of AlertDialogs scattered among the code with slight differences between them in terms of actions, appearance or something else.
public class DialogWrapper extends DialogFragment {
private static final String ARG_DIALOG_ID = "ARG_DIALOG_ID";
private int mDialogId;
/**
* Display dialog fragment.
* #param invoker The fragment which will serve as {#link AlertDialog} alert dialog provider
* #param dialogId The ID of dialog that should be shown
*/
public static <T extends Fragment & DialogProvider> void show(T invoker, int dialogId) {
Bundle args = new Bundle();
args.putInt(ARG_DIALOG_ID, dialogId);
DialogWrapper dialogWrapper = new DialogWrapper();
dialogWrapper.setArguments(args);
dialogWrapper.setTargetFragment(invoker, 0);
dialogWrapper.show(invoker.getActivity().getSupportFragmentManager(), null);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDialogId = getArguments().getInt(ARG_DIALOG_ID);
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return getDialogProvider().getDialog(mDialogId);
}
private DialogProvider getDialogProvider() {
return (DialogProvider) getTargetFragment();
}
public interface DialogProvider {
Dialog getDialog(int dialogId);
}
}
When it comes to Activity you can invoke getContext() inside onCreateDialog(), cast it to the DialogProvider interface and request a specific dialog by mDialogId. All logic to dealing with a target fragment should be deleted.
Usage from fragment:
public class MainFragment extends Fragment implements DialogWrapper.DialogProvider {
private static final int ID_CONFIRMATION_DIALOG = 0;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
Button btnHello = (Button) view.findViewById(R.id.btnConfirm);
btnHello.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DialogWrapper.show(MainFragment.this, ID_CONFIRMATION_DIALOG);
}
});
}
#Override
public Dialog getDialog(int dialogId) {
switch (dialogId) {
case ID_CONFIRMATION_DIALOG:
return createConfirmationDialog(); //Your AlertDialog
default:
throw new IllegalArgumentException("Unknown dialog id: " + dialogId);
}
}
}
You can read the complete article on my blog How to prevent Dialog being dismissed? and play with the source code.
It seems that this is still an issue, even when "doing everything right" and using DialogFragment etc.
There is a thread on Google Issue Tracker which claims that it is due to an old dismiss message being left in the message queue. The provided workaround is quite simple:
#Override
public void onDestroyView() {
/* Bugfix: https://issuetracker.google.com/issues/36929400 */
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
Incredible that this is still needed 7 years after that issue was first reported.
You can combine the Dialog's onSave/onRestore methods with the Activity's onSave/onRestore methods to keep the state of the Dialog.
Note: This method works for those "simple" Dialogs, such as displaying an alert message. It won't reproduce the contents of a WebView embedded in a Dialog. If you really want to prevent a complex dialog from dismissal during rotation, try Chung IW's method.
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
myDialog.onRestoreInstanceState(savedInstanceState.getBundle("DIALOG"));
// Put your codes to retrieve the EditText contents and
// assign them to the EditText here.
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Put your codes to save the EditText contents and put them
// to the outState Bundle here.
outState.putBundle("DIALOG", myDialog.onSaveInstanceState());
}
I had a similar problem: when the screen orientation changed, the dialog's onDismiss listener was called even though the user didn't dismiss the dialog. I was able to work around this by instead using the onCancel listener, which triggered both when the user pressed the back button and when the user touched outside of the dialog.
In case nothing helps, and you need a solution that works, you can go on the safe side, and each time you open a dialog save its basic info to the activity ViewModel (and remove it from this list when you dismiss dialog). This basic info could be dialog type and some id (the information you need in order to open this dialog). This ViewModel is not destroyed during changes of Activity lifecycle. Let's say user opens a dialog to leave a reference to a restaurant. So dialog type would be LeaveReferenceDialog and the id would be the restaurant id. When opening this dialog, you save this information in an Object that you can call DialogInfo, and add this object to the ViewModel of the Activity. This information will allow you to reopen the dialog when the activity onResume() is being called:
// On resume in Activity
override fun onResume() {
super.onResume()
// Restore dialogs that were open before activity went to background
restoreDialogs()
}
Which calls:
fun restoreDialogs() {
mainActivityViewModel.setIsRestoringDialogs(true) // lock list in view model
for (dialogInfo in mainActivityViewModel.openDialogs)
openDialog(dialogInfo)
mainActivityViewModel.setIsRestoringDialogs(false) // open lock
}
When IsRestoringDialogs in ViewModel is set to true, dialog info will not be added to the list in view model, and it's important because we're now restoring dialogs which are already in that list. Otherwise, changing the list while using it would cause an exception. So:
// Create new dialog
override fun openLeaveReferenceDialog(restaurantId: String) {
var dialog = LeaveReferenceDialog()
// Add id to dialog in bundle
val bundle = Bundle()
bundle.putString(Constants.RESTAURANT_ID, restaurantId)
dialog.arguments = bundle
dialog.show(supportFragmentManager, "")
// Add dialog info to list of open dialogs
addOpenDialogInfo(DialogInfo(LEAVE_REFERENCE_DIALOG, restaurantId))
}
Then remove dialog info when dismissing it:
// Dismiss dialog
override fun dismissLeaveReferenceDialog(Dialog dialog, id: String) {
if (dialog?.isAdded()){
dialog.dismiss()
mainActivityViewModel.removeOpenDialog(LEAVE_REFERENCE_DIALOG, id)
}
}
And in the ViewModel of the Activity:
fun addOpenDialogInfo(dialogInfo: DialogInfo){
if (!isRestoringDialogs){
val dialogWasInList = removeOpenDialog(dialogInfo.type, dialogInfo.id)
openDialogs.add(dialogInfo)
}
}
fun removeOpenDialog(type: Int, id: String) {
if (!isRestoringDialogs)
for (dialogInfo in openDialogs)
if (dialogInfo.type == type && dialogInfo.id == id)
openDialogs.remove(dialogInfo)
}
You actually reopen all the dialogs that were open before, in the same order. But how do they retain their information? Each dialog has a ViewModel of its own, which is also not destroyed during the activity lifecycle. So when you open the dialog, you get the ViewModel and init the UI using this ViewModel of the dialog as always.
Yes, I agree with the solution of using DialogFragment given by #Brais Gabin, just want to suggest some changes to the solution given by him.
While defining our custom class that extends DialogFragment, we require some interfaces to manage the actions ultimately by the activity or the fragment that has invoked the dialog. But setting these listener interfaces in the onAttach(Context context) method may sometimes cause ClassCastException that may crash the app.
So to avoid this exception, we can create a method to set the listener interfaces and call just it after creating the object of the dialog fragment.
Here is a sample code that could help you understand more-
AlertRetryDialog.class
public class AlertRetryDialog extends DialogFragment {
public interface Listener{
void onRetry();
}
Listener listener;
public void setListener(Listener listener)
{
this.listener=listener;
}
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder=new AlertDialog.Builder(getActivity());
builder.setMessage("Please Check Your Network Connection").setPositiveButton("Retry", new
DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
//Screen rotation will cause the listener to be null
//Always do a null check of your interface listener before calling its method
if(listener!=null&&listener instanceof HomeFragment)
listener.onRetry();
}
}).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
return builder.create();
}
}
And in the Activity or in the Fragment you call-
AlertRetryDialog alertRetryDialog = new AlertRetryDialog();
alertRetryDialog.setListener(HomeFragment.this);
alertRetryDialog.show(getFragmentManager(), "tag");
And implement the methods of your listener interface in your Activity or the Fragment-
public class YourActivity or YourFragment implements AlertRetryDialog.Listener{
//here's my listener interface's method
#Override
public void onRetry()
{
//your code for action
}
}
Always make sure that you do a null check of the listener interfaces before calling any of its methods to prevent NullPointerException (Screen rotation will cause the listener interfaces to be null).
Please do let me know if you find this answer helpful. Thank You.
Just use
ConfigurationChanges = Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize
and app will know how to handle rotation and screen size.