DialogFragment's inflated view being reused in next instance - java

I have a dialog fragment with a custom view. The background color of the custom view also changes depending on the passed argument from newInstance() method. To make certain that it's really a different DialogFragment instance, I also passed another argument to the newInstance() method, the dialog title. The background color can also accept a null value, in which case, no background color is set.
Now here is the problem: when I show the dialog fragment with the null value passed for the background color the second time, the inflated view still has the background color of the last shown dialog fragment even though the title is already different. Why is the system reusing the last inflated view? Shouldn't the background color be empty? What am I not understanding properly?
MyDialogFragment.java
public static MyDialogFragment newInstance(String title, Integer bgColor) {
MyDialogFragment df = new MyDialogFragment();
Bundle args = new Bundle();
args.putString(TITLE_ARG, title);
args.putSerializable(BG_ARG, bgColor);
df.setArguments(args);
return df;
}
...
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
Integer bgColor = (Integer) args.getSerializable(BG_ARG);
View view = getActivity().getLayoutInflater().inflate(R.layout.my_dialog_fragment, null);
// check if null was passed
if(bgColor != null)
((GradientDrawable) view.getBackground()).setColor(bgColor);
return new AlertDialog.Builder(getActivity())
.setTitle(args.getString(TITLE_ARG))
.setView(view)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, null)
.create();
}
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fm = getSupportFragmentManager();
MyDialogFragment df = (MyDialogFragment) fm.findFragmentByTag(MyDialogFragment.TAG);
if(df == null) {
df = MyDialogFragment.newInstance("title1", Color.GREEN);
df.show(fm, MyDialogFragmentTAG);
}
}
});
findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fm = getSupportFragmentManager();
MyDialogFragment df = (MyDialogFragment) fm.findFragmentByTag(MyDialogFragment.TAG);
if(df == null) {
df = MyDialogFragment.newInstance("title2", null);
df.show(fm, MyDialogFragmentTAG);
}
}
});
}

The first click of button1 will create dialog and attach it to the FragmentManager.
The click of button2 (second click) will find your old fragment, so df should not be null, and you should not see any dialog.
Maybe your snippet of code is incomplete.

Related

How do I connect a button to a Dialog that will edit a TextView, within a FrameLayout, with a users input?

I don't understand DialogFragment at all. How to create one, how to get the user input out of it, and set it into a TextView.
I would like for the TITLE button, when clicked, to bring up a DialogFragment asking the user to enter the title of their Mood Board. The user enters a title. When they click the PostiveButton "Done", the user's title is set into the top left frame of the mood board, which has a TextView with a hint.
Please! Ask questions, because I don't really understand the dialog setup.
Here is a picture of my main_layout, in my MainActivity. Every element has an "#+id/".
The solution you are looking for is a callback:
Create an interface with a method to use as a callback
Implements the interface on the activity
Create the dialog fragment and in onAttach get the interface
Show the dialog fragment on the activity
On dismiss the dialog fragment pass the text using the instance of the interface
interface Callback {
updateText(String text)
}
class CoolActivity... implements Callback
onCreate {
//find your views
showDialogBtn.setOnClickListener(...
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
Fragment prev = getSupportFragmentManager().findFragmentByTag("yourTag");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
DialogFragment dialogFragment = ExampleDialogFragment.newInstance();
dialogFragment.show(ft, "yourTag");
)
}
#Override
updateText(String text) {
youtView.setText(text)
}
class CoolDialogFragment extend DialogFragment {
private Callback callback;
#Override
void onAttach(Context context) {
callback = (Callback) context
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = new Dialog(getActivity());
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.dialog_fragment_example, container, false);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//find the views in the dialog fragment
closeBtn.clickListener(...
callback.updateText(edittext.getText().toString())
dismiss()
)
}
}
Here is a gist of a dialog fragment
https://gist.github.com/cutiko/7e307efcf7492ea52ef05cd90f9e3203
The problem is you want to connect a dialog fragment with a another component, and you want to do it straigth forward. This is not considered a good practice because yiu create 2 componentes higly attached, so the best would be to use data persistence and some form of react programming
You can make your mood board title textview static then call it to the alertdialog with edittext to set it text (setText)
like this.
final EditText edittext = new EditText(MainActivity.this);
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setMessage("Input Title")
.setView(edittext)
.setCancelable(false)
.setPositiveButton("Confirm", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
YourCustomDialog.your_title_textviewMoodboard.setText(edittext.getText().toString());
}
})
.setNegativeButton("Back", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
}
});
AlertDialog alert = builder.create();
alert.show();
in your custom dialog. declare your textview static globally
public static TextView your_title_textviewMoodboard;

Using Bundle to send data from activity to fragment

I am trying to use Bundle to send data from an activity to a fragment. The activity is receiving the input from a dialogbox when the user clicks on the actionbar add icon. The button also opens the dialogbox but it sends the data straight to the fragment (I'm trying to learn the difference between activity and fragment and to interact with the dialogfragment). None of the solutions on the internet have worked for me, and I was hoping someone can help
I have provided a visualization to aid in my explanation of the issue. So initially, I click the action add icon that opens the dialogbox (2nd pic), when I enter an input, it doesn't alter the data on the fragment. Only when I press the action add icon for a second time, does the first input get updated (3rd pic). Also you may notice that it says "Bundle{[Dialog Input = First Input]}" where First Input is the user input. How do I change this to just, First Input. I tried clearing the textview before setting the value, but that doesn't work. Now lastly when I press the button, it opens the dialogbox and when I enter in data, the data from the action add icon (handled in activity then data sent to fragment) overlaps with the data from the button (data sent straight to fragment). Any help would be appreciated. Thanks in advance.
MainActivity:
public class MainActivity extends AppCompatActivity implements
MyCustomDialog.OnInputSelected{
public String dialogInput;
FragmentManager fragmentManager;
#Override
public void sendInput(String input) {
dialogInput = input;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentManager = getSupportFragmentManager();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
//Inflate the menu, this adds items to the action bar if it is present
getMenuInflater().inflate(R.menu.menu, menu);
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
//Handle action bar clicks here. The action bar will automatically handle clicks on the home/up button
//so long as you specify a parent activity in AndroidManifest.xml
switch(item.getItemId()){
case R.id.action_add:
MyCustomDialog dialog = new MyCustomDialog();
dialog.show(getSupportFragmentManager(), "MyCustomDialog");
//Trying Bundle to pass data, dialog input between activity and fragment
Bundle bundle = new Bundle();
bundle.putString("Dialog Input", dialogInput);
//Set Fragment class arguments
MainFragment fragment = new MainFragment();
fragment.setArguments(bundle); //set argument bundle to fragment
fragmentManager.beginTransaction().replace(R.id.MainFragment,fragment).commit(); //now replace Mainfragment
Toast.makeText(this, "Action_Add Clicked Successfully", Toast.LENGTH_SHORT).show();
}
return super.onOptionsItemSelected(item);
}
}
MainFragment:
public class MainFragment extends Fragment implements MyCustomDialog.OnInputSelected{
TextView InputDisplay;
Button OpenDialog;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
InputDisplay = view.findViewById(R.id.InputDisplay);
OpenDialog = view.findViewById(R.id.Open_Dialog);
//Getting Main Activity dialog information with Bundle, that was received from toolbar add
Bundle bundle = getArguments();
if(bundle != null){
String dialogInput = bundle.toString();
//Clearing since Fragment call and activity call overlap each other.
InputDisplay.setText("");
InputDisplay.clearComposingText();
InputDisplay.setText(dialogInput);
}
//String dialogInput = this.getArguments().getString("Dialog Input");
OpenDialog.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("MainFragment", "onClick: opening dialog");
MyCustomDialog customDialog = new MyCustomDialog();
customDialog.setTargetFragment(MainFragment.this, 1);
customDialog.show(getFragmentManager(), "MyCustomDialog");
}
});
return view;
}
#Override
public void sendInput(String input) {
InputDisplay.setText("");
InputDisplay.setText(input);
}
}
My Custom Dialog:
public class MyCustomDialog extends DialogFragment {
private EditText Input;
private TextView ActionOK, ActionCANCEL;
private OnInputSelected onInputSelected;
public interface OnInputSelected{
void sendInput(String input);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try{
Fragment onInputSelected_fragment = getTargetFragment();
Activity onInputSelected_activity = getActivity();
if(onInputSelected_fragment != null){
onInputSelected = (OnInputSelected) onInputSelected_fragment;
}else{
onInputSelected = (OnInputSelected) onInputSelected_activity;
}
//throw new RuntimeException("Custom Dialog onAttach Listener was NULL");
}catch(ClassCastException e){
Log.e("Custom Dialog", "onAttach: ClassCastException: " + e.getMessage());
}
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_my_custom, container, false);
Input = view.findViewById(R.id.Input);
ActionOK = view.findViewById(R.id.Action_OK);
ActionCANCEL = view.findViewById(R.id.Action_CANCEL);
ActionCANCEL.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getDialog().dismiss();
}
});
ActionOK.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onInputSelected.sendInput(Input.getText().toString());
getDialog().dismiss();
}
});
return view;
}
}
How do I change this to just, First Input.
your output is printed like this "Bundle{[Dialog Input = First Input]}" because you are directly doing bundle.toString(); instead of getting the value you have stored in the bundle.
change the above to this
String dialogInput = bundle.getString("Dialog Input")
InputDisplay.setText(dialogInput);
the data from the action add icon overlaps with the data from the button
Clear the existing text in the text view before setting the new value like this
String dialogInput = bundle.getString("Dialog Input")
InputDisplay.setText(");
InputDisplay.setText(dialogInput);
Also, I noticed that all the variable names that you have used are not following camel case I suggest you correct that as well.

How to update custom RecyclerView from FragmentDialog?

I have an Activity A with a fragment frag2. Inside the fragment I have a RecyclerView and Adapter to show a list of custom class objects. Adding objects to the adapter is handled programmatically. I have a button inside TwoFragment that opens a FragmentDialog. I'd like to add an object to my Adapter by confirming this dialog, but it seems that the adapter is null when called from the FragmentDialog.
The same adapter is not null, and works if I call it from the fragment OnClick.
Moreover the adapter is null only after screen rotation, it works fine before rotating.
To communicate between the two Fragments I implement a communicator class in activity A.
Activity A
public void respond(String type) {
frag2.addSupport(type);
}
frag2
public RecyclerView rv;
public ArrayList<support> supports;
public myAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supports = new ArrayList<>();
adapter = new myAdapter(supports);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = inflater.inflate( R.layout.fragment_two, container, false);
layout.setId(R.id.frag2);
if (savedInstanceState!=null)
{
supports = savedInstanceState.getParcelableArrayList("supports");
}
rv = (RecyclerView) layout.findViewById(R.id.rv);
adapter = new myAdapter(supports);
rv.setAdapter(myAdapter);
rv.setLayoutManager(new LinearLayoutManager(getActivity()));
rv.setItemAnimator(new DefaultItemAnimator());
#Override
public void onClick(View v) {
int id = v.getId();
switch (id){
case R.id.button1:
addSupport(type); // THIS WORKS ALWAYS, even after screen rotate
break;
case R.id.button2:
showDialog();
break;
}
}
public void showDialog(){
FragmentManager manager = getFragmentManager();
myDialog dialog = new myDialog();
dialog.show(manager, "dialog");
}
public void addSupport(String type){
adapter.addItem(new support(type)); // this line gives null pointer on adapter, but only if called after screen rotate and only if called from the dialog
}
dialog
communicator comm;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog, null);
comm = (myCommunicator) getActivity();
create = (Button) view.findViewById(R.id.button_ok);
create.setOnClickListener(this);
return view;
}
#Override
public void onClick(View v) {
if(v.getId()==R.id.button_ok)
{
// some controls to set type
comm.respond(type)
dismiss();
}
else {
dismiss();
}
myAdapter
public class myAdapter extends RecyclerView.Adapter<myAdapter.VH> {
private LayoutInflater inflater;
private ArrayList<support> data = new ArrayList<>();
// settings for viewholder
public myAdapter (ArrayList<support> data)
{
this.data=data;
}
public void addItem(support dataObj) {
data.add(dataObj);
notifyItemInserted(data.size());
}
}
logcat
FATAL EXCEPTION: main
java.lang.NullPointerException: Attempt to invoke virtual method 'myAdapter.addItem(myObject)' on a null object reference
I hope there are no mistakes, I shortened the code for better understanding. Keep in mind that everything works if I never rotate the screen.
I'm a beginner with android and I'm stuck with this for several days now. Please, help.
To understand the problem, it's as you say:
.. everything works if I never rotate the screen
So firstly to understand what happens on rotation, this is a quote from the Android Developer website:
Caution: Your activity will be destroyed and recreated each time the user rotates the screen. When the screen changes orientation, the system destroys and recreates the foreground activity because the screen configuration has changed and your activity might need to load alternative resources (such as the layout).
Ok, now to understand the error:
FATAL EXCEPTION: main
java.lang.NullPointerException: Attempt to invoke virtual method 'myAdapter.addItem(myObject)' on a null object reference
Essentially, in your dialog class, you have created a strong dependency by declaring :
comm = (myCommunicator) getActivity();
because comm references objects which would have been destroyed on rotation, hence the NullPointerException.
To further understand runtime changes, such as orientation changes, I'd recommend going through Handling Runtime Changes.
Update
Thank you for your answer, what would you recommend instead of comm = (myCommunicator) getActivity(); ?
The solution comes in 3 parts:
Make sure the onCreate of Activity A has the following:
#Override
public void onCreate(Bundle savedInstanceState) {
......
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
frag2 = (Frag2) fm.findFragmentByTag(“frag2”);
// create frag2 only for the first time
if (frag2 == null) {
// add the fragment
frag2 = new Frag2();
fm.beginTransaction().add(frag2 , “frag2”).commit();
}
......
}
Add setRetainInstance(true) to the onCreate of frag2.
Remove the implicit referencing i.e. comm = (myCommunicator) getActivity();, and implement something more loosely coupled for dialog.
dialog
public interface Communicator {
void respond(String type);
}
Communicator comm;
....
public void addCommunicator(Communicator communicator) {
comm = communicator;
}
public void removeCommunicator() {
comm = null;
}
#Override
public void onClick(View v) {
if((v.getId()==R.id.button_ok) && (comm!=null))
{
// some controls to set type
comm.respond(type);
}
// Regardless of what button is pressed, the dialog will dismiss
dismiss();
}
This allows you do the following in frag2 (or any other class for that matter):
frag2
<pre><code>
public class Frag2 extends Fragment implements dialog.Communicator {
........
public void showDialog() {
FragmentManager manager = getFragmentManager();
myDialog dialog = new myDialog();
dialog.addCommunicator(this);
dialog.show(manager, "dialog");
}
#Override
public void respond(String type){
adapter.addItem(new support(type));
}
}

How to set the value of TextView in Custom Dialog in Android

I want to set the value of TextView in custom dialog box after I clicked in one of the fragments in Activity. Dialog was running fine with custom text view after it is being called. The problem is that it always shows NullPointerException when I tried to call changeMessageText("Hello") function.
ProgressDialogFragment.java
public class ProgressDialogFragment extends DialogFragment {
private TextView txtMessage;
private AlertDialog.Builder mBuilder;
public static ProgressDialogFragment newInstance(AlertDialog.Builder builder){
ProgressDialogFragment progressDialogFragment = new ProgressDialogFragment();
progressDialogFragment.mBuilder = builder;
return progressDialogFragment;
}
#Nullable
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = getLayoutInflater(savedInstanceState).inflate(R.layout.dialog_progress, null);
txtMessage = (TextView) view.findViewById(R.id.txtMessage);
changeMessageText("Hello World by default"); // this one works
mBuilder.setView(view);
return mBuilder.create();
}
public void changeMessageText(String text){
txtMessage.setText(text);
}
}
Sample Code after the button Clicked
AlertDialog.Builder builder = new AlertDialog.Builder(this);
ProgressDialogFragment progressDialogFragment = ProgressDialogFragment.newInstance(builder);
progressDialogFragment.show(getSupportFragmentManager(),"progress_dialog");
// dialog box shows until the following function is called.
progressDialogFragment.changeMessageText("Hello");
When progressDialogFragment.changeMessageText("Hello"); called, txtMessage is not created yet.
1) Add private String message; to ProgressDialogFragment.
2) Change changeMessageText
public void changeMessageText(String text){
message = text;
if(txtMessage != null){
txtMessage.setText(text);
}
}
3) add after mBuilder.setView(view);
if(message!=null && !message.isEmpty()){
txtMessage.setText(message);
}
4) Remove changeMessageText("Hello World by default"); in onCreateDialog
And. It Doesn't Work for Me.
View view = getLayoutInflater(savedInstanceState).inflate(R.layout.dialog_progress, null);
I change it.
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.dialog_progress, null);
Hope it will help you.

How to Update a ProgressDialog Fragment

I need to update a progress dialog fragment from inside an AsyncTask. I know how to do this using the old ProgressDialog, but now this way of showing dialogs is deprecated and I'm trying to do the same with Fragments.
This is the fragment code:
public static class CustomProgressDialogFragment extends SherlockDialogFragment {
static CustomProgressDialogFragment newInstance(Object... params){
CustomProgressDialogFragment df = new CustomProgressDialogFragment();
Bundle bundle = new Bundle();
// Populate bundle with params (not shown)
df.setArguments(bundle);
return df;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setCancelable(false);
int style = DialogFragment.STYLE_NORMAL, theme = 0;
setStyle(style,theme);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle bundle = this.getArguments();
ProgressDialog dialog = new ProgressDialog(getActivity());
// Get params from bundle and customize dialog accordingly (not shown)
dialog.setCancelable(false);
dialog.setIndeterminate(false);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
return dialog;
}
}
and this is how I show it:
FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction();
CustomProgressDialogFragment dialogFragment = CustomProgressDialogFragment.newInstance(<My params here>);
ft.disallowAddToBackStack();
ft.add(dialogFragment, stringSet.tag);
ft.commitAllowingStateLoss();
Now I need to call incrementProgressBy over the dialog from the AsyncTask. Is there a way to retrieve the underlying dialog from the FragmentManager? What would be the proper way of doing this? (I don't want to leak references if the dialogs gets recreated).
Thanks in advance.
This can be achieved calling DialogFragment.getDialog(). The DialogFragment can be retrieved as usual, with FragmentManager.findFragmentByTag.

Categories