Reputation: 1636
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
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
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
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