x-treme
x-treme

Reputation: 1636

Reuse a fragment in Android

I have a fragment which has a TextView, an EditText and a Button. I also have 2 activities which include this fragment and at onClick of the button in one of the activities, the other is started. Via the intent, the text in the edittext is passed which becomes the text of the textview of the other activity.

I had two design decisions to choose from

  1. Create two such fragments classes with appropriate methods that construct the appropriate intents. Access the UI elements from inside the respective fragment object and start the activities.
  2. Create only one fragment class. onClick the, event is passed down to a particular method in the activities (both the activities have this method) and the activities have the logic to build the intent and start the other activity

Consider what would happen if there are 100 such activities. The first method would have us write 100 different fragment classes with custom methods, but in the second method, it is a single class and the activities have the custom logic in a particularly named method.

Therefore I chose to go with the second choice and I realized that the UI elements could not be instantiated in the onCreate method of activity as the fragment's layout is not inflated yet. I am doing the instantiation in onStart as a workaround.

Is that bad practice or is there a better design pattern to follow?

Upvotes: 0

Views: 2955

Answers (2)

x-treme
x-treme

Reputation: 1636

I decided to settle with the following patter --

Any activity which includes this fragment should implement an interface like

public interface ViewsCreatedListener {
    public void onViewsCreated();
}

The activity would then look like

public class ExampleActivity extends Activity implements ViewsCreatedListener {
     .
     .
     .
     .
     @Override
     public void onViewsCreated() {
          //Initiate the views here and do what gotta be done
     }
}

The fragment should check that any activity that includes this fragment should implement that interface using the onAttach method and onActivityCreated, the activity is notified

public class ExampleFragment extends Fragment {

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

     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
         listener.onViewsCreated();
     }

}

Doing this way, the fragment just provides the UI and the including activities decide as to what should be done with the UI elements included via the fragment. This maximizes reusability.. DRY... :-D

Upvotes: 0

marcus.ramsden
marcus.ramsden

Reputation: 2633

The recommended pattern is to create a holder interface which any activity that wants to instantiate your fragment must implement. Also to set data for views in your new fragment then create a newInstance() factory method on your fragment.

I tend to approach it like this;

class FooFragment implements Fragment {

    private static final String TEXT_FOR_TEXTVIEW = "textForTextView";

    private FooFragmentHolder mHolder;

    /*
     * Rather than creating your fragment in your layout directly
     * you should instead instantiate it using this class in your 
     * activity.
     */
    public static FooFragment newInstance(String text) {
         Bundle data = new Bundle();
         data.putString(TEXT_FOR_TEXTVIEW, text);
         FooFragment fooFragment = new FooFragment();
         fooFragment.setArguments(data);
         return fooFragment;
    }

    public interface FooFragmentHolder {
         public void buttonPressed(String editTextContent);
    }

    /*
     * When we create the fragment with the activity we use onAttach to get
     * our holder implementation (the activity)
     */
    @Override
    public void onAttach(Activity activity) {
         if (activity instanceof FooFragmentHolder) {
             mHolder = (FooFragmentHolder) activity;
         } else {
             throw new IllegalStateException("Containing activity must implement FooFragmentHolder");
         }
    }

    @Override
    public void onCreateView(Inflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_foo, container, false);

        final EditText editText = (EditText) view.findViewById(R.id.edit_text);
        Button button = (Button) view.findViewById(R.id.button);
        button.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(Button button) {
                mHolder.buttonPressed(editText.getText());
            }

        })};
        TextView textView = (TextView) view.findViewById(R.id.text_view);

        Bundle args = getArguments();
        if (args != null) {
            textView.setText(args.getString(TEXT_FOR_TEXTVIEW));
        }

        return view;
    }

}

Now in your activity you just need to implement the FooFragmentHolder interface and use the newInstance method we created;

class FooActivity extends Activity implements FooFragment.FooFragmentHolder {

    private static final String TEXT_FOR_TEXTVIEW = "textForTextView";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentLayout(R.layout.activity_foo);

        // Instead of creating your fragment in your layout, create a holder
        // layout and attach a new instance of your fragment using a fragment
        // transaction.
        FooFragment fooFragment = FooFragment.newInstance(getIntent().getStringExtra(TEXT_FOR_TEXTVIEW));
        getFragmentManager().beginTransaction()
            .replace(R.id.content, fooFragment)
            .commit();
    }

    @Override
    public void buttonPressed(String editTextContent) {
        // In this case just starting the next FooActivity, but logic could be
        // applied for any other activity.
        Intent intent = new Intent(this, FooActivity.class)
            .putExtra(TEXT_FOR_TEXTVIEW, editTextContent);
        startActivity(intent);
    }

}

Upvotes: 2

Related Questions