How to get data from DialogFragment to a Fragment? - android

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,'.

Related

How to get fragment tag or ID?

I have 2 fragments which are instantiated from the same class as the layouts are identical like so:
getSupportFragmentManager().beginTransaction().
add(R.id.leftContainer,new LeftFragmentClass(),"leftFrag").commit();
getSupportFragmentManager().beginTransaction().
add(R.id.rightFrag,new LeftFragmentClass(),"rightFrag").commit();
Within LeftFragmentClass there is a callback method which is called when the button within the fragment is pressed. After this some processing is done and data is displayed, however, right now the callback cannot distinguish which button was pressed. Is there a function which can return which fragment button was pressed?
For this type of condition i create a function inside fragment which will return me the instance of fragment and make the fragment constructor private something like:-
public class LeftFragmentClass extends Fragment{
private String fragmentTag = null;
public LeftFragmentClass(){}
public static LeftFragmentClass newInstance(String tag){
LeftFragmentClass mLeftFragmentClass = new LeftFragmentClass();
Bundle bundle = new Bundle();
bundle.putString ("tag",tag);
mLeftFragmentClass.setArgument(bundle);
return mLeftFragmentClass;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
tag = getArguments().getString("tag")
}
}
So i used newInstance function to create instance of LeftFragmentClass and pass the tag to it which i m setting to Fragment argument using bundle and inside onCreate get bundle using getArguments and from it the tag value. Pass this tag value as one of the parameter to your callback method to identify which button was clicked.
So from activity for getting instance of LeftFragmentClass you can write as
LeftFragmentClass mLeftFragmentClassLeft = LeftFragmentClass.newInstance("left")
LeftFragmentClass mLeftFragmentClassRight = LeftFragmentClass.newInstance("Right")
==== Edit ====
keep the fragment class constructors always public don't make it private as i suggested above in my sample code. Making it private will cause application to crash with exception
java.lang.RuntimeException: Unable to start activity
ComponentInfo{MainActivity}:
android.support.v4.app.Fragment$InstantiationException: Unable to
instantiate fragment com.thatswhy.AppAlertDialog: make sure class name
exists, is public, and has an empty constructor that is public
Fragment fragment = getFragmentManager().findFragmentByTag("Tag")
As per provided the info you can do something like this, in your callback method pass the button object and check accordingly,
Some code snippet to explain the same :
Suppose your callback method is onButtonClick() then you can pass button object like :
public void onButtonClick(Button button){
// check here with button id
if(button.getId() == R.id.button1) {
} else if(button.getId() == R.id.button1) {
}
}
Hope this makes things clear..
The cleanest way of doing this I've seen is to create two distinct View.OnClickListener(s) in the Activity.
Have a getter() for each. public View.OnClickListener getLeftButtonPressed(), public View.OnClickListener getRightButtonPressed()
Then when you instantiate your left and right instances of your fragment, just pass in the appropriate 'View.OnClickListener' to the constructor of the Fragment. This not only reduces the code in the Fragment(s), it also centralizes the 'logic' of what to do when buttons are pressed.
public class MyActivity extends Activity {
// create the two listeners
View.OnClickListener leftButtonListener = new View.OnClickListener() {
public void onClick(View v) {
leftButtonClicked(v);
}
});
View.OnClickListener rightButtonListener = new View.OnClickListener() {
public void onClick(View v) {
rightButtonClicked(v);
}
});
// 2 getters
public View.OnClickListener getLeftListener() { return this.leftButtonListener; }
public View.OnClickListener getRightListener() { return this.rightButtonListener; }
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.content_layout_id);
}
// actual logic of what to do when each button is pressed.
private void leftButtonClicked(View v){
// some logic here
}
private void rightButtonClicked(View v){
// some logic here
}
}
This removes you later having to keep track of which button was pressed by making use of strings and if/then/else blocks, etc.
Add a parameter to interface callback function in your fragment;
interface Interfacecallback{
public void callbackfunction(int fragid);
}
Interfacecallback interfacecallback;
//in your button click
//pass 1 for fragment right
//pass 2 for fragment left
interfacecallback.callbackfunction(1);
You can check the fragment tag using this line of code if it exists:-
Fragment mapFragment = getFragmentManager().findFragmentByTag("MapRestaurantFragment");

Android DialogFragment crash on screen rotation

I have a DialogFragment class. I have to set the listener every time it shown (It has multiple cases in my app).
But when I rotate the screen mListener becomes null and there is a NullPointerExcpetion when I click a button. I can't implement the listener in the activity because it has a few cases for this dialog, each has different action.
The CustomDialog class:
MyDialogListener mListener;
public void show(FragmentManager fm, MyDialogListener listener) {
mListener = listener;
super.show(fm, "MyDialog");
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle("Title")
.setPositiveButton(android.R.string.ok, new OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int whichButton) {
mListener.onDialogPositiveClick();
// NullPointerException after a screen rotate
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
The activity class:
public void showMyFirstDialog() {
new CutsomDialog().show(getFragmentManager(), mFirstListener);
}
public void showMySecondDialog() {
new CutsomDialog().show(getFragmentManager(), mSecondListener);
}
You cannot preserve instance fields of a Fragment (including a DialogFragment). The mechanism for having local data survive configuration changes is to set the fragment's arguments to a Bundle that contains your data; this bundle will survive configuration changes.
First, eliminate the show() method; it's not the correct approach. Instead, you can do something like this:
DialogFragment frag = new MyDialogFragment();
Bundle args = new Bundle();
args.putString("TITLE", "Dialog Title Goes Here");
args.putString("MESSAGE", "This is a dialog messaage");
frag.setArguments(args);
frag.show();
Then you can retrieve the title and message when you create the AlertDialog:
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
String title = args.getString("TITLE");
String message = args.getString("MESSAGE");
// set up and return the alert dialog as before
}
Dealing with the DialogListener is a little more complex. You don't want to be holding a reference to that across config changes because it will lead back to the destroyed activity. Instead, you can arrange to retrieve the listener from the activity inside the fragment's onAttach() method:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// now cast activity to your activity class and get a reference
// to the listener
}
You may need to change your activity class(es) a bit to get this to work right. If you're using this dialog fragment from many activities, it's particularly helpful here to define an interface that the activities can implement to request a listener. It would then look something like this:
public interface DialogListenerProvider {
DialogListener getDialogListener();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof DialogListenerProvider) {
mListener = ((DialogListenerProvider) activity).getDialogListener();
} else {
// throw an error
}
}
The listener should not be passed in as an argument but instead implemented as part of interface both within the dialogfragment itself and may be an activity. That way, when the positive / negative click happens, you can update data on something and pass it to listener. The listener, when implemented by activity, would pass on the data to teh activity and you can take corresponding action in activity then.
Check these few examples -
http://www.i-programmer.info/programming/android/7426-android-adventures-custom-dialogs-using-dialogfragment.html?start=2
http://android-developers.blogspot.com/2012/05/using-dialogfragments.html
Hope it helps.

How Send message from fragment to activity and received and use in activity?

Please please don't minus my question i confused when googling.
I used Android Tab Layout with Swipeable Views in my code for when user pressed setting button on an activity.
now I need send message from TopRatedFragment.java that extends from fragment to the activity that call the mainActivity of "Android Tab Layout with Swipeable Views".
You can do this by implementing a call back
create an interface first
public interface CommunicationInterface {
public void onSuccess();
public void onFailed();
}
then in your activity implement the interface
public class YourActivity extends ActionBarActivity implements CommunicationInterface {
//default functions
#Override
public void onSuccess() {
//stuff you want to do in the acivity
}
#Override
public void onFailed() {
//stuff you want to do in the acivity
}
}
Now in the fragment
public class yourfragment extends Fragment {
CommunicationInterface callback;
//stuffs that usually come in yor fragment and like OncreateView etc
#Override
public void onActivityCreated(#Nullable Bundle outState) {
super.onActivityCreated(outState);
//after all the stuff you want to do in your fragment then implement //call back function to communicate with the activity
callback= (CommunicationInterface) getActivity();
callback.onSuccess();//according to your purpose use where ever you like
callback.onFailed();//according to your purpose use where ever you like
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
callback= (CommunicationInterface) activity;
}
}
Take a close look on this reference:
Creating event callbacks to the activity
The android docs recommend using this pattern of having the parent activity implement an interface of the fragment (Basically calling methods on it)
class MyFragment extends Fragment {
interface Listener {
public void onSomeEvent();
}
private void somethingHappeninInTheFragment() {
// let the activity know
((Listener) getActivity()).onSomeEVent();
}
}
class MyActivity extends Activity implements MyFragment.Listener {
// etc
#Override
public void onSomeEvent() {
// handle the message from the fragment
}
}
Explained with a more concrete example here: http://developer.android.com/guide/components/fragments.html#EventCallbacks
Here's the solution:
Step 1 : From your fragment.
Intent i = new Intent(getActivity(), YourActivity.class);
i.putExtra("key", "Your value1");
i.putExtra("key2", "Your value2");
i.putExtra("key3", "Your value3");
getActivity().startActivity(i);
Step 2 : In your Activity where you want the result
Intent getResults = getIntent();
String firstValue = getResults.getStringExtra("key1");
String secondValue = getResults.getStringExtra("key2");
String thirdValue = getResults.getStringExtra("key3");
Use those values your needs are.
Hope this helps.. :)

Receive result from DialogFragment

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!
}

Update fragment on dialog fragment option selection

I have fragment that on a component click pop-ups DialogFragment. This dialog fragment holds list of options. When an option from list is selected I want to notify fragment so I can run fields update procedure.
I did something like this
#Override
public void onClick(DialogInterface dialog, int item) {
updateSharedPreference(item);
Log.e("ProfilePersonaListDialog", "Click on dialog, inside onClick");
OnCloseListDialogListener act = (OnCloseListDialogListener) getActivity();
act.onDialogListSelection();
dismiss();
}
However this getActivity() calls on FragmentActivity and not the fragment that triggered the dialog fragment.
I could kill currently open/running fragment and call a new instance that would get updated fields, but that is dirty solution that I would prefer to avoid.
Any suggestions how to go about this update of fragment once option selected in dialog fragment?.
Just coming back with solution. My problem was actually forwarding current fragment getTag() string as parameter of show() for DialogFragment. If anyone interested here is working sample.
Create simple listener
public interface OnCloseListDialogListener {
public void onDialogListSelection();
}
Create new dialog that will extend DialogFragment
public class ListDialogFragment extends DialogFragment implements DialogInterface.OnClickListener {
private PersonaData[] mPersonaData;
private String[] mPersonaName;
private final String TAG;
public static ListDialogFragment newInstance(PersonaData[] personaData, String tag) {
ListDialogFragment dialog = new ListDialogFragment(personaData, tag);
Bundle bundle = new Bundle();
dialog.setArguments(bundle);
return dialog;
}
private ListDialogFragment(PersonaData[] personaData, String tag) {
this.mPersonaData = personaData.clone();
this.TAG = tag;
}
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setCancelable(true);
int style = DialogFragment.STYLE_NORMAL, theme = 0;
setStyle(style, theme);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.dialog_title);
mPersonaName = getData();//Your own implementation here
builder.setNegativeButton("Cancel", this);
builder.setSingleChoiceItems(mPersonaName, -1, new SingleChoiceListener());
return builder.create();
}
#Override
public void onClick(DialogInterface dialogInterface, int i) {
}
private class SingleChoiceListener implements DialogInterface.OnClickListener {
#Override
public void onClick(DialogInterface dialog, int item) {
updateSharedPreference(item);
OnCloseListDialogListener act = (OnCloseListDialogListener) getFragmentManager().findFragmentByTag(TAG);
act.onDialogListSelection();
dismiss();
}
}
}
And then in fragment from which you wish to call this dialog do as bellow. DIALOG is just String constant I put there just dialog
SOME_CLICKABLE.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager manager = getFragmentManager();
ListDialogFragment dialog = ListDialogFragment.newInstance(mPersona, getTag());
dialog.show(manager, DIALOG);
}
});
It is necessary in most cases that a Fragment be aware that it is running under the context of an Activity of some description and acceptable for the child Fragment to invoke a method on an interface implicitly implemented by the parent Activity (as demonstrated by the cast in your code snippet). When you get your references working as Tomasz points out you'll be golden.
However, :) to aid the re-usability of the dialog fragment I would suggest that you leverage BroadcastReceivers. A BroadcastReceiver simply broadcasts a message saying I did 'x'. The parent activity or in indeed any other top level component can then declare I am listening for 'x'. Once, the event has been fired in the dialog component, this event will be collected by the parent Activity's onReceive where you can run the necessary code to update your fields.
On a personal level, I prefer this loose coupling over the casting interface approach since it forces me to think about the purpose of each Fragment and keep it modular.
If you want to give it a shot then have a read over the dev guide section on BroadcastReceivers and follow the follow steps;
Implement the BroadcastReceiver in your parent activity. Notice an onReceive method is required to be implemented.
Override the parent Activity's onResume method and register the the activity as a receiver of an event with intent action "blah". Something like;
#Override
protected void onResume() {
super.onResume();
registerReceiver(this, new IntentFilter("blah"));
}
Override the parent Activity's onPause method an unregister the activity as the receiver so as to avoid 'leaked receivers' (you'll find out).
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(deleteSpotReceiver);
}
In your DialogFragment onClick fire the event which your parent activity is 'listening' for.
#Override
public void onClick(DialogInterface dialog, int item) {
updateSharedPreference(item);
Log.e("ProfilePersonaListDialog", "Click on dialog, inside onClick");
final Intent intent = new Intent();
intent.setAction("blah");
getActivity().sendBroadcast(intent);
dismiss();
}
The parent activity will collect the message and you can continue processing. Let me know if you decide to adopt that method.
Just the way you did it above and add sth like that in your activity :
public void onDialogListSelection() {
AnotherFragment anotherFragment = (AnotherFragment) getSupportFragmentManager()
.findFragmentById(R.id.anotherFragment);
anotherFragment.customMethodToNotifyListHasBeenSelected();
}
Of course, if you are not use Support Library then call getFragmentManager instead of getSupportFragmentManager.

Categories

Resources