0

Answer

The answer / an example can be found at the bottom of this post. The way this answer has been reached, can be found in this answer and its comments.


Original question

I've got a dialogFragment, which has two editText input fields. On a specific buttonClick, I want this dialogFragment to be created, and the two editText fields to be filled with text. For this purpose, I created a simple method inside the dialogFragment's class.

public void presetFields(String nameField, String tagField) {
    nameInputField.setText(nameField);
    tagInputField.setText(tagField);
}

Problem is, that both nameInputField and tagInputField get initialised inside the onCreateDialog method.

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    LayoutInflater inflater = getActivity().getLayoutInflater();
    final View v_iew = inflater.inflate(R.layout.fragment_inputdialog, null);

    nameInputField = (EditText) v_iew.findViewById(R.id.inputdialogname);
    tagInputField = (EditText) v_iew.findViewById(R.id.inputdialogtag);
}

I thought that would be no problem at all, since I have a similar construction running for a fragment. Only difference is that the editText's get initialised in an onCreateView instead of an onCreateDialog.

This is the onClick code that shows the dialogFragment and calls the method to set the nameInputField and tagInputField fields.

editButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        stuffManagerInputDialogFragment.show(getFragmentManager(), "TEST");
        stuffManagerInputDialogFragment.presetFields(nameTextViewContent, tagTextViewContent);
    }
});

When I click the editButton, this is the log I get with the NPE

03-02 16:04:25.120 509-509/com.example.tim.timapp I/art: Not late-enabling -Xcheck:jni (already on)
03-02 16:04:25.190 509-509/com.example.tim.timapp W/System: ClassLoader referenced unknown path: /data/app/com.example.tim.timapp-2/lib/x86_64
03-02 16:04:25.400 509-524/com.example.tim.timapp D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
03-02 16:04:25.520 509-524/com.example.tim.timapp I/OpenGLRenderer: Initialized EGL, version 1.4
03-02 16:04:25.570 509-524/com.example.tim.timapp W/EGL_emulation: eglSurfaceAttrib not implemented
03-02 16:04:25.570 509-524/com.example.tim.timapp W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0x7fceb5b27dc0, error=EGL_SUCCESS
03-02 16:04:28.660 509-509/com.example.tim.timapp D/TEST: 2131493026
03-02 16:04:30.310 509-509/com.example.tim.timapp D/AndroidRuntime: Shutting down VM
03-02 16:04:30.310 509-509/com.example.tim.timapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.tim.timapp, PID: 509
    java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.EditText.setText(java.lang.CharSequence)' on a null object reference
    at com.example.fragments.MainFragments.DialogFragments.StuffManagerInputDialogFragment.presetFields(StuffManagerInputDialogFragment.java:149)
    at com.example.fragments.MainFragments.VariableFragments.StuffManagerVariableFragment$2.onClick(StuffManagerVariableFragment.java:87)
    at android.view.View.performClick(View.java:5198)
    at android.view.View$PerformClick.run(View.java:21147)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:148)
    at android.app.ActivityThread.main(ActivityThread.java:5417)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
03-02 16:04:33.140 509-509/com.example.tim.timapp I/Process: Sending signal. PID: 509 SIG: 9

I'm not an expert by far, but what I make of this error, is that nameTextView and tagTextView are null at the moment that I'm trying to set text to them, which makes me think the method gets called before the fragment is created.

How can I make sure that both editText's are properly initialised before I try to call something on it?

I have removed some bits of code for easy reading. If something seems to be missing, it probably is. Please let me know, so I can add it.
If you require more code, please also let me know.


Complete answer

All credits go to George Mulligan for this one.

So some stuff has changed from the original approach. First of all, the presetFields method has been removed. The text that should populate the editText fields is now passed to the fragment as an argument.

The onClick method now looks like this:

editButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        nameTextViewContent = nameTextView.getText().toString();
        tagTextViewContent = tagTextView.getText().toString();

        Bundle args = new Bundle();
        args.putString("name", nameTextViewContent);
        args.putString("tag", tagTextViewContent);
        stuffManagerInputDialogFragment.setArguments(args);
        stuffManagerInputDialogFragment.show(getFragmentManager(), "TEST");
    }
});

So without the presetFields method, we have no way to set the editText fields to the text we want. This has been added to the onCreateDialog method, as can be seen below.

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    LayoutInflater inflater = getActivity().getLayoutInflater();
    final View v_iew = inflater.inflate(R.layout.fragment_inputdialog, null);

    nameInputField = (EditText) v_iew.findViewById(R.id.inputdialogname);
    tagInputField = (EditText) v_iew.findViewById(R.id.inputdialogtag);

    if (getArguments() != null) {
        nameInputField.setText(getArguments().getString("name"));
        tagInputField.setText(getArguments().getString("tag"));
    }
}

The null-check for getArguments() has been added because I sometimes need to call this dialogFragment without filling the editText fields. When this happens, it means there are no arguments set to the fragment, which would result in a NPE when calling getArguments().getString("String");

That should be it. I do however have a reputation for being stupid, so it might very well be possible I overlooked something, that should be included in this explanation. If so, please let me know, so I can add it.

Community
  • 1
  • 1
Timmiej93
  • 1,328
  • 1
  • 16
  • 35

2 Answers2

1

What I make of this error, is that nameTextView and tagTextView are null at the moment that I'm trying to set text to them, which makes me think the method gets called before the fragment is created.

That is almost correct. The Fragment is created but onCreateDialog() has not been called yet because showing the dialog happens asynchronously after the call to show(...).

You can fix this by adding the two preset Strings as arguments to the DialogFragment.

You can then assign the EditText fields the correct value directly in the onCreateDialog method by using getArguments() on the DialogFragment.

Here is what this looks like:

public MyDialogFragment extends DialogFragment {
    private static final String ARG_NAME = "name";
    private static final String ARG_TAG = "tag";
    public MyDialogFragment newInstance(String name, String tag) {
        Bundle args = new Bundle();
        args.putString(ARG_NAME, name);
        args.putString(ARG_TAG, tag);

        MyDialogFragment frag = new MyDialogFragment();
        frag.setArguments(args);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        LayoutInflater inflater = getActivity().getLayoutInflater();
        final View v_iew = inflater.inflate(R.layout.fragment_inputdialog, null);

        nameInputField = (EditText) v_iew.findViewById(R.id.inputdialogname);
        tagInputField = (EditText) v_iew.findViewById(R.id.inputdialogtag);

        nameInputField.setText(getArguments().getString(ARG_NAME, ""));
        tagInputField.setText(getArguments().getString(ARG_TAG, ""));
    }
}

Then the new click event handler.

editButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        stuffManagerInputDialogFragment = MyDialogFragment
             .newInstance(nameTextViewContent, tagTextViewContent);

        stuffManagerInputDialogFragment.show(getFragmentManager(), "TEST");
    }
});
Community
  • 1
  • 1
George Mulligan
  • 11,813
  • 6
  • 37
  • 50
  • @Timmiej93 I added an example of how this works. Only part I wasn't sure on is where you are currently creating the `DialogFragment` so in the example I just put it in the click handler for now. – George Mulligan Mar 02 '16 at 16:03
  • I got it pretty much working with your method. Only problem is, that I want the `DialogFragment`'s `editText` fields to be empty sometimes, and filled on other times. I figured I could do this by adding a boolean to the arguments I send to the `DialogFragment`, and check it by using `if (getArguments().getBoolean("booleanName"))`. However, when I haven't sent any arguments to the `DialogFragment`, I get a NPE. How would I catch this null? Simply using an `if (getArguments().getBoolean("boolArgs") == null)` won't work ("Operator '==' cannot be applied to 'boolean', 'null'") – Timmiej93 Mar 02 '16 at 16:08
  • `getArguments()` really just returns a bundle. The `getBoolean` method can take a second argument that is the default value if there is no value set for the given key. So you can just do `getArguments().getBoolean("boolArgs", "false");`. Alternatively why don't you just pass in empty strings to the newInstance method when creating the fragment? Then the EditText's will be populated with the empty string as you want without needing the extra boolean argument. – George Mulligan Mar 02 '16 at 16:12
  • On second thought, I could of course just check if either the `name` or `tag` argument is null, and work from there. EDIT: Both are very good points. Gonna work from there, thanks. – Timmiej93 Mar 02 '16 at 16:13
  • I think your last comment there is getting at what my alternative method is. I would go with empty strings instead of null so you don't need to do the null checks. – George Mulligan Mar 02 '16 at 16:15
  • Hmm, curious. `if (getArguments().getBoolean("boolArgs", false))` still throws a NPE when the boolean `boolArgs` is null. Kinda does make sense, but doesn't on the other hand. Same thing goes for `.getString()`. – Timmiej93 Mar 02 '16 at 16:17
  • Only way that is possible is if you are creating the `DialogFragment` without using the `newInstance(...)` method and the fragment's arguments is null. Check if you are using `new MyDialogFragment()` anywhere outside of the `newInstance(...)` method. – George Mulligan Mar 02 '16 at 16:27
  • I'm so stupid. I was working without using `newInstance()` (never use it, didn't even know about it). The error I was getting clearly said: _Attempt to invoke virtual method 'java.lang.String android.os.Bundle.getString(java.lang.String)' on a null object reference_, also known as: `getArguments()` is null, which IS checkable. That should do the trick! – Timmiej93 Mar 02 '16 at 16:29
0

Don't try to manipulate your dialog immediately after calling show.

If you want to set default values for fields within your dialog, pass them to the dialog as arguments and initialise them during the dialog creation.

e.g

public class MyFragment extends Fragment {

/**
 * Static factory method that takes an int parameter,
 * initializes the fragment's arguments, and returns the
 * new fragment to the client.
 */
public static MyFragment newInstance(int index) {
    MyFragment f = new MyFragment();
    Bundle args = new Bundle();
    args.putInt("index", index);
    f.setArguments(args);
    return f;
}

}

from: http://www.androiddesignpatterns.com/2012/05/using-newinstance-to-instantiate.html

Kuffs
  • 35,581
  • 10
  • 79
  • 92