I am new to Android and I am trying to build an app containing three fragments: let's say A, B, and C. I want a button on A to show me B when clicked, and a button on B to show me C when clicked. I understand that one way is to use FragmentManager like this: in fragment A, I can have a button click listener that does
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, new B())
.commit();
However, I do not want A to know about fragment B, or B to know about C. I was thinking of creating some kind of FragmentController to solve this problem such that the Controller would know about the necessary transitions and maintain a state machine.
Is this a common pattern in Android? I tried googling but I didn't see many code examples for this pattern. How can I decouple the different fragments so that the fragments don't have to worry about the transitions?
Thanks.
Yes, it is a common pattern, you need understand how to work the comunnication between fragments.
Taking on, your fragments are contained in an activity, maybe you can do the next:
The Fragment listener
public interface FragmentNavigationListener {
public void onNavigateTo(int fragment);
}
The Activity which implements callback
public static final int Fragment FRAGMENT_A = 0;
public static final int Fragment FRAGMENT_B = 1;
#Override
public void onNavigateTo(int fragment){
switch fragment{
case FRAGMENT_A :
...
case FRAGMENT_B :
...
}
}
Each Fragment
must encore that parent activity implements the listener
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (FragmentNavigationListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement FragmentNavigationListener");
}
}
In OnclickListener from each button in each fragment you call the listener.
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
mCallback.onNavigateTo(FRAGMENT_B);
}
});
Write fragmentInteraction interfaces, define methods in this interface, then onAttach of the fragment lifeCycle do this
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnFragmentInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
That way every activity to which this fragment gets attached to must implement the OnFragmentInteractionListener. Now in your onClick method do mListener.onClick(view) assuming you have a method called onClick(View v) defined in your interface. And in your activity's implementation of the onClick function do the fragmentTransaction
Related
Here is my situation. I have a fragment which has two buttons on it. When you tap on either button, a DialogFragment appears which contains a single EditText with ok/cancel buttons. Both buttons open the same DialogFragment, but the data input into the EditTexts needs to be kept separate.
I recently started implementing the fragment event callback pattern from the Android docs seen here, but have run into an issue - I have two buttons using the same event callback and am not sure how to differentiate which one the user has just finished using. So using the docs as an example, I can open FragmentA from two buttons on the same screen, but need to handle the result differently depending on which button I clicked.
In my fragment:
public static class FragmentA extends DialogFragment {
public interface OnEditNameListener {
public void onPositiveButtonClicked(String newName);
}
}
#Override
public void onAttach(Context context){
super.onAttach(context);
try {
mListener = (OnEditNameListener ) context;
}
catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement OnEditNameListener ");
}
}
In my Activity, which implements the OnEditNameListener:
button1.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View view) {
(new EditNameDialog.Builder())
.setTitle(getContext().getString(R.string.title))
.setValue(currentText)
.show(mParentFragmentActivity.getSupportFragmentManager());
}
});
button2.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
(new EditNameDialog.Builder())
.setTitle(getContext().getString(R.string.title2))
.setValue(currentText2)
.setInputType(InputType.TYPE_CLASS_NUMBER)
.show(mParentFragmentActivity.getSupportFragmentManager());
}
});
#Override
public void onPositiveButtonClicked(String newName) {
... //does stuff with the name.
//Currently no way to determine whether this came from button1 or button2.
}
Currently, both callbacks hit the same OnPositiveButtonClicked method with the input from the DialogFragment, but I do not know how to determine which of the two buttons this came from.
First of all you have to add an argument to your onPositiveButtonClicked(String name,int buttonId),and the pass to FragmentA an argument based on the button pressed like:
FragmentA fragment=new FragmentA();
Bundle args = new Bundle();
args.putInt("buttonId", 1 or 2);
fragment.setArguments(args);
//open the fragment from the activity
Then in your FragmentA onCreate method try:
int buttonId = getArguments().getInt("buttonId");
and finally when positive button pressed call:
onPositiveButtonClicked(newName,buttonId)
UPDATE
An even better solution is to create a setter in your DialogFragment and use anonymus interfaces like:
(new EditNameDialog.Builder())
.setTitle(getContext().getString(R.string.title))
.setValue(currentText).setOnEditNameListener(new EditNameListener{
#Override
onPositiveButtonClicked(String newName){
//handle action
}
});
And in your DialogFragment add the setter:
EditNameListener listener;
public DialogFragment setOnEditNameListener(EditNameListener listener){
this.listener=listener;
return this;
}
I've a activity which basically is :
public class FragmentContainer extends FragmentActivityBase implements IRefreshListener {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getExtras() == null
|| getIntent().getExtras().get("type") == null) {
showProductList();
}
else
{
if (getIntent().getExtras().get("type").equals("customer"))
showCustomerList();
}
#Override
public void showProductList() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
// load the product list
ProductList fragment = new ProductList();
fragmentTransaction.replace(R.id.fragment_container, fragment)
.addToBackStack(null);
fragmentTransaction.commit();
}
.....
}
in the fragment, I use onCreateView to get intent and then I create my view.
If I need to change the fragment, I get the reference to the parent Activity (taken from onAttach) and I call method referenced by the IRefreshListener.
like :
IRefreshListener mCallback;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception.
try {
mCallback = (IRefreshListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement IRefreshListener");
}
}
public void callCustomer() {
mCallback.showCustomerList();
}
It works but whne I change the orientation, even I use setRetainInstance(true) it will be reseted.
I have 2 questions :
Do I use the good pattern to manage my application. The big activity which contains one fragment become bigger with the time
How should I handle orientation change ?
Regards
I do not find this pattern is more perfect or best one, although it is or was a suggestion from Google. Because it could be a worse coding style if fragment knows particular activity or listeners, you might write more and more code, when you wanna to let your fragment know more its "container" or "parents". Will the fragment later be used for other activity which has not been implemented with IRefreshListener etc, you will code much more.
My introduce is using Otto-Bus or Event-Bus. You can just send message from one to one. Every one doesn't have to know each other.
For Fragment-Activity communication, this is the suggested way of doing it, by using a listener.
In my case I have two fragments and a button at each and I would like them to do the exact same thing when pressed.
Should I create a separate listener class that the Activity implements and then instantiate a listener in each fragment or there is a better design that I am not aware of?
EDIT
I am sorry, I probably didn't communicate that properly. I am not looking for communication between fragments. I have a Fragment A with a buttonA and a Fragment B with a buttonB. When I click on buttonA, there is a listener in my Activity and method doSomething() is called. Now I want buttonB calling doSomething() too. Should I A) create a second listener and have the activity implement that too, B) create one separate listener class and use this one for both or C) a better choice ??
For communication between fragment to frament or activity to fragment communication via events. There are few alternatives are there e.g. this otto eventbus I know. and the tutorial about this can be found Here or just google it.
As from the documentation :
Two Fragments should never communicate directly.
So you best follow the pattern explained in the article and communicate thru the activity on which the fragments are attached.
When a listener is called from fragment A then get the fragment B from the fragmentManger
YourFragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_b);
fragment.doSomething();
I'm using this pattern and it works well for me:
public class Fragment1 extends Fragment {
FragmentListener mCallback;
public interface FragmentListener {
public void onAction1();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof FragmentListener) {
mCallback = (FragmentListener) activity;
}
}
public void onAction2() {
// do your stuff...
}
}
public class Fragment2 extends Fragment {
FragmentListener mCallback;
public interface FragmentListener {
public void onAction2();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof FragmentListener) {
mCallback = (FragmentListener) activity;
}
}
public void onAction1() {
// do your stuff...
}
}
public class MainActivity implements Fragment1.FragmentListener, Fragment2.FragmentListener {
private Fragment1 fragment1;
private Fragment2 fragment2;
/**
* Listening to events from first fragment and forwarding to second fragment
*/
#Override
public void onAction1() {
fragment2.onAction1();
}
/**
* Listening to events from second fragment and forwarding to first fragment
*/
#Override
public void onAction2() {
fragment1.onAction2();
}
}
The Activity listens to "events" from the fragments and if needed forward it to the other fragment(s).
I've got 2 Fragments, Fragment A and Fragment B. I added Fragment B over Fragment A, by using FragmentTransaction().add, which means Fragment A is underlying Fragment B. Is there a way to change the data in Fragment A after I did something on Fragment B and pressed the Back button from Fragment B? I wish to have a generic way to notify the Fragment A. Because it may be another Fragment being overlaid. I tried using FragmentTransaction.replace() - it works fine for refreshing the previous page.
Just overwrite onBackPressed() in your activity and your fragment and do your required calls there.
More to callbacks / communication with other fragments can be found here:
Communicating with Other Fragments
public class FragmentA extends Fragment {
public void updateMyself(String updateValue){
Log.v("update", "weeee Fragment B updated me with" + updateValue);
}
}
public class FragmentB extends Fragment {
public Interface FragmentBCallBackInterface {
public void update(String updateValue);
}
private FragmentBCallBackInterface mCallback;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (FragmentBCallBackInterface) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement FragmentBCallBackInterface");
}
//As an example we do an update here - normally you wouln't call the method until your user performs an onclick or something
letsUpateTheOtherFragment();
}
private void letsUpateTheOtherFragment(){
mCallback.update("This is an update!);
}
}
public class MyActivity extends Activity implements FragmentInterfaceB {
#Override
public void update(String updateValue){
FragmentA fragmentA = (FragmentA) getSupportFragmentManager().findFragmentById(R.id.article_fragment);
if (fragmentA != null) {
fragmentA.updateMyself(updateValue);
} else {
//replace the fragment... bla bla check example for this code
}
}
}
I have two Fragments in my Activity: fragment A with button X and fragment B with button Y.
How can I change button X's background image when I click button B? Is it possible?
From the documentation,
Because each fragment defines its own layout and its own behavior with its own lifecycle callbacks, you can include one fragment in multiple activities, so you should design for reuse and avoid directly manipulating one fragment from another fragment.
That being said, what you want to do is create event callbacks to the activity. A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary. This is the recommended way to share events between two separate Fragments--that is, sharing the event through the activity.
Check out the link above... it provides a couple nice examples. If you are still having trouble, let me know and maybe I can be more explicit.
Edit #1:
Let's say you click a button in fragment A and you want this to cause changes to a button in fragment B. Here's some sample code illustrating the concept:
The callback interface:
public interface OnButtonClickedListener {
public void onButtonClicked();
}
The activity:
public class SampleActivity extends Activity implements OnButtonClickedListener {
/* Implementation goes here */
public void onButtonClicked() {
// This method is called from fragment A, and when it is called,
// it will send information to fragment B. Remember to first
// check to see if fragment B is non-null.
/* Make call to a method in fragment B that will update its display */
}
}
Fragment A:
public class FragmentA extends Fragment {
OnButtonClickedListener mListener;
/* Implementation goes here */
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnButtonClickedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnButtonClickedListener ");
}
}
public void clickButton() {
// When the button is clicked, notify the activity.
// The activity will then pass the information to fragment
// B (if it has been created).
mListener.onButtonClicked();
}
}
Edit #2:
Now, you might be wondering, "Why would anyone ever go through all of this trouble? What's the point of creating a separate activity callback method when you could just have fragment A directly manipulate fragment B?"
The main reason you want to do this is to ensure that each fragment is designed as a modular and reusable activity component. This is especially important because a modular fragment allows you to change your fragment combinations for different screen sizes. When designing your application to support both tablets and handsets, you can reuse your fragments in different layout configurations to optimize the user experience based on the available screen space. For example, on a handset, it might be necessary to separate fragments to provide a single-pane UI when more than one cannot fit within the same activity. Making use of activity callbacks ensures that you will easily be able to reuse your fragments in situations where fragment B is not visible on the screen. For example, if you are on a handheld device and there is not enough room to display fragment B, then you can easily have your activity check to see if fragment B is currently being shown on the screen.
Sorry if this isn't clear... I'm finding it difficult to describe :P. Working your way through this tutorial might help... Activity callbacks make your life especially easier as a developer when you are working with interactive multi-pane layouts.
Base on Alex Lockwood's answer:
The activity:
public class SampleActivity extends Activity{
public interface OnButtonClickedListener {
public void onButtonClicked();
}
private OnButtonClickedListener onButtonClickedListener = null;
public OnButtonClickedListener getOnButtonClickedListener () {
return onButtonClickedListener
}
public void setOnButtonClickedListener (
OnButtonClickedListener onButtonClickedListener {
this.onButtonClickedListener = onButtonClickedListener;
}
}
Fragment A:
public class FragmentA extends Fragment {
private OnButtonClickedListener onButtonClickedListener = null;
private OnClickListener actionBarClickListener = new OnClickListener() {
#Override
public void onClick(View view) {
if (onButtonClickedListener == null){
onButtonClickedListener = ((SampleActivity) getActivity()).onButtonClickedListener ();
}
if (onButtonClickedListener != null) {
onButtonClickedListener
.onButtonClicked();
}
}
};
}
Fragment B:
public class FragmentB extends Fragment {
private OnButtonClickedListener onButtonClickedListener = new OnButtonClickedListener() {
#Override
public void onButtonClicked() {
Toast.makeText(getActivity(), "Button clicked", Toast.LENGTH_SHORT).show();
}
};
#Override
public void onResume() {
super.onResume();
SampleActivity sampleActivity = (SampleActivity) getActivity();
sampleActivity.setSearchBoxTextChangedListener(onButtonClickedListener);
}
}
Hope can help someone.
Setting the onClick attribute for a button in your layout, even your fragment's layout, will call the appropriate method on your Activity.
Your app can then send this signal from your Activity to fragment B.