Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a listener to a custom Fragment in Android

I'm creating a view pager in my app and using a class that extends Fragment on it. When I create an instance I can pass all the elements (an image, text, etc) and store it with the Bundle to use it in the onCreate. But I can't store a listener for the button in the fragment. Here is my class:

public class RegWizardFragmentInfo extends Fragment {

private static final String IMAGE = "image";
private static final String TEXT = "text";
private static final String BUTTON = "buttonText";
private View.OnClickListener buttonCallBack;

private Button button;
private int image;
private int text;
private int buttonText;


public RegWizardFragmentInfo newInstance(int image, int text, int buttonText, View.OnClickListener callback) {

    RegWizardFragmentInfo fragment = new RegWizardFragmentInfo();
    Bundle bundle = new Bundle();
    bundle.putInt(IMAGE, image);
    bundle.putInt(BUTTON, buttonText);
    bundle.putInt(TEXT, text);
    fragment.setArguments(bundle);
    fragment.setRetainInstance(true);
    return fragment;

}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    super.onActivityCreated(savedInstanceState);
    this.image = getArguments().getInt(IMAGE);
    this.text = (getArguments() != null) ? getArguments().getInt(TEXT)
            : -1;
    this.buttonText = (getArguments() != null) ? getArguments().getInt(BUTTON)
            : -1;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    ViewGroup rootView = (ViewGroup) inflater.inflate(
            R.layout.fragment, container, false);
    //Extract all the views and add the image and texts

    return rootView;

}

So, how can I store the listener that I get in the newInstance to add it to the button on the onCreateView method?

Thanks for the help.

like image 842
Jaime Alcántara Arnela Avatar asked Oct 05 '17 13:10

Jaime Alcántara Arnela


3 Answers

You can use a callback in your Fragment:

public class RegWizardFragmentInfo extends Fragment {

    private Button button;

    private OnClickCallback callback;

    public interface OnClickCallback {
        void onClick();
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        callback = (OnClickCallback) context;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                callback.onClick();
            }
        });
    }
}

and implement this new interface in your parent Activity

like image 93
Óscar Avatar answered Sep 21 '22 13:09

Óscar


The other answers assign the listener in onAttach. While this will work, it requires that the calling Activity (and not, say, an anonymous class) implement your interface. Moreover, it forces you to cast the Context given to you in onAttach to an instance of your interface, which can cause crashes and is generally considered bad form. You might instead create a method to set the listener inside your Fragment:

public class RegWizardFragmentInfo extends Fragment {

    private OnClickListener mListener;

    public interface OnClickListener {
        void onClick();
    }

    /**
     * Call at any time after this fragment has been constructed.
     */
    public void setListener(OnClickListener listener) {
        mListener = listener;
    }

    /* ...other stuff... */
}

I can think of three disadvantages to this approach:

  1. You need to call an extra method every time you want to instantiate the Fragment.
  2. You can't guarantee that mListener is set at any time. You may need to pepper your Fragment code with null checks.
  3. You need to be careful to make sure the listener remains set after lifecycle events such as screen rotation.
like image 33
Jack Meister Avatar answered Sep 20 '22 13:09

Jack Meister


I suppose you want to re-use the Fragment with various different listeners. So your approach is not ideal since you can not use the Bundle for that. A better approach would be to use the callback design pattern e.g.

public class RegWizardFragmentInfo extends Fragment {

    public interface RegWizardCallback {
        void onClick();
    }
}

Your Activity would implement that interface. Since a Fragment only lives inside an Activity you can get the callback instance from it by using the lifecycle method onAttach(). It would look like this

public class RegWizardFragmentInfo extends Fragment {
    private RegWizardCallback callback;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            callback = (RegWizardCallback) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement RegWizardCallback ");
        }
    }

    public interface RegWizardCallback {
        void onClick();
    }
}

With that you can simply call callback.onClick inside the listener of the Button.

like image 36
Murat Karagöz Avatar answered Sep 22 '22 13:09

Murat Karagöz