David
David

Reputation: 163

Custom dialog interface for multiple Activities/ Fragments

I am new to android studio and I am trying implement a dialogfragment to pop up whenever a button is clicked or a toolbar action_add button is clicked. It sends data from the dialog box to a fragment or an activity (trying to understand how it works for both). My dialog class has an interface, which worked great when I implemented it in the fragment, but the activity is a bit different. I am using the interface to pass the data to the activity, then I am using Bundle to transfer the data from the activity to the fragment. I believe the error occurs in the onAttach since I have getTargetFragment();

Is it possible to have multiple activities/fragments implement a single interface? If so, how do I cater to both the activity and fragment in the interface, onAttach, and the sending of data?

Thank you guys in advance, below is my code for the custom_dialog class for the dialogbox, and a fragment which is attached to an activity. The goal is either press the button in the fragment or the toolbar on the activity to open a dialog and get an input from the user, that will be transferred to be displayed.

Error:

Process: com.example.andrewg.dialogfragment, PID: 13335
java.lang.NullPointerException: Attempt to invoke interface method 'void com.example.andrewg.dialogfragment.MyCustomDialog$OnInputSelected.sendInput(java.lang.String)' on a null object reference
    at com.example.andrewg.dialogfragment.MyCustomDialog$2.onClick(MyCustomDialog.java:58)
    at android.view.View.performClick(View.java:6597)
    at android.view.View.performClickInternal(View.java:6574)
    at android.view.View.access$3100(View.java:778)
    at android.view.View$PerformClick.run(View.java:25881)
    at android.os.Handler.handleCallback(Handler.java:873)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6649)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:826)

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);

    //Redundant
    MenuItem actionMenuItem = menu.findItem(R.id.action_add);
    actionMenuItem.setOnMenuItemClickListener(new 
MenuItem.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem menuItem) {
            return false;
        }
    });
    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 to pass dialog input into an intent to send to the 
fragment
            /*Intent intent = new Intent(getApplicationContext(), 
MainFragment.class);
            intent.putExtra("Dialog Input", dialogInput);
            startActivity(intent);*/
            //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();
        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(input);
}
}

Custom Dialog: I added a second interface variable for activity for onAttach to use getActivity(), but it doesn't seem right.

public class MyCustomDialog extends DialogFragment {

private EditText Input;
private TextView ActionOK, ActionCANCEL;

public OnInputSelected onInputSelected_Fragment;
public OnInputSelected onInputSelected_Activity;

public interface OnInputSelected{
    void sendInput(String input);
}

@Override
public void onAttach(Context context) {
    try{
        onInputSelected_Fragment = (OnInputSelected) getTargetFragment();
        onInputSelected_Activity = (OnInputSelected) getActivity();
    }catch(ClassCastException e){
        Log.e("Custom Dialog", "onAttach: ClassCastException: " + e.getMessage());
    }
    super.onAttach(context);
}

@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_Fragment.sendInput(Input.getText().toString());
            onInputSelected_Activity.sendInput(Input.getText().toString());

            getDialog().dismiss();
        }
    });

    return view;
}
}

Upvotes: 1

Views: 1405

Answers (2)

Bö macht Blau
Bö macht Blau

Reputation: 13019

Is it possible to have multiple activities/fragments implement a single interface?

Yes it is. You just have to be aware that getActivity() will return null if the DialogFragment is not attached to an Activity but to another Fragment whereas getTargetFragment() will return null if there is no target Fragment, e.g. when you show the dialog directly from your Activity and so did not call setTargetFragment().

Since casting null to anything does not result in an Exception, all you have to do in your code is a null check before actually calling an interface method.

But because you will not have a target Fragment and an Activity showing the same instance of your DialogFragment, you can change your code and do with just one field

private OnInputSelected onInputSelected;

Then in onAttach() you check which one is null - Activity or target Fragment - and set onInputSelected correctly once and for all.

try{
    Fragment onInputSelected_Fragment = getTargetFragment();
    Activity onInputSelected_Activity = getActivity();
    if (onInputSelected_Fragment != null){
        onInputSelected = (OnInputSelected)onInputSelected_Fragment;
    }
    else {
        onInputSelected = (OnInputSelected)onInputSelected_Activity;
    }
    // throw RuntimeException here if onInputSelected still is null
    //
}catch(ClassCastException e){
    Log.e("Custom Dialog", "onAttach: ClassCastException: " + e.getMessage());
}

Note that onInputSelected might be null if you forgot to set a target Fragment when showing the dialog from a Fragment. This would be a programming error, so maybe you want to throw a RuntimeException in this case. If your app makes it past this point, you have something implementing the interface, mission accomplished.

In your onClick() you can simply write

onInputSelected.sendInput(Input.getText().toString());

Upvotes: 1

Bakon Jarser
Bakon Jarser

Reputation: 721

getActivity() is returning null. According to the answer here the getActivity() will return null until after onAttach is run. If you move super.onAttach() to the beginning of your onAttach method then it should return the activity correctly.

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    try{
        onInputSelected_Fragment = (OnInputSelected) getTargetFragment();
        onInputSelected_Activity = (OnInputSelected) getActivity();
    }catch(ClassCastException e){
        Log.e("Custom Dialog", "onAttach: ClassCastException: " + e.getMessage());
    }

}

Upvotes: 0

Related Questions