I have a fragment that can either be attached to an Activity or a parent fragment. This fragment has an interface that must be implemented by anyone it is attached to. For activities, this is quite simple:
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Activity){
Activity activity =(Activity) context;
try {
mCallback = (OnMyListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() +
" must implement OnMyListener");
}
}
}
However, I am unable to set the mCallback listener for other Fragments that are hosting this particular Fragment.
You can't directly communicate between 2 fragments, it has to go thorough the activity hosting it (and I saw you already implemented the first half).
After the activity received the data from sender fragment, you can send it to the receiver fragment by resolving the receiver fragment's reference first, using:
ReceiverFragment fragment = ( ReceiverFragment) getSupportFragmentManager().findFragmentById(R.id.receiver_fragment_id);
if it's null then you need to instantiate it first and pass the data using fragment.setArguments(Bundle), otherwise you can directly call the member function of the receiver fragment.
Check: https://developer.android.com/training/basics/fragments/communicating.html
Related
I have a Fragment A which has an interface that will return the value of a TextView. The Fragment A is initialized and Attached to FragmentB. Here is the code for Fragment A and B.
Fragment A
public class FragmentA extends Fragment {
...
public interface Listener {
void onValue(int value);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Listener) {
listener = (Listener) context;
} else {
throw new RuntimeException(context.toString() + " must implement Listener");
}
}
}
Fragment B
public class FragmentB extends Fragment implements FragmentA.Listener {
...
private void initFragmentA() {
FragmentManager fragmentManager = getChildFragmentManager();
fragmentA = FragmentA.newInstance();
fragmentManager.beginTransaction().add(container, fragmentA, TAG).commit();
}
#Override
public void onValue(int value) {
}
}
When I start the app, a error occurred:
Java.lang.RuntimeException: ####.page.MainActivity#1f7f316c must implement Listener
at ####.widget.FragmentA.onAttach(FragmentA.java:66)
The MainActivity contains the Fragment B, but the Fragment B has already implemented the interface of Fragment A. Why the error occurred? The interface implemented in parent Fragment doesn't work?
From Android Developer website, it says:
Often you will want one Fragment to communicate with another, for example to change the content based on a user event. All Fragment-to-Fragment communication is done through the associated Activity. Two Fragments should never communicate directly.
To allow a Fragment to communicate up to its Activity, you can define an interface in the Fragment class and implement it within the Activity. The Fragment captures the interface implementation during its onAttach() lifecycle method and can then call the Interface methods in order to communicate with the Activity.
Therefore, when you implement that interface in Fragment B, it throws a RuntimeException error.
Summary: you have to implement that interface in your fragment hosting activity.
You can learn more on how to pass data between fragments thru a hosting activity (usually we make use of Bundle) at http://www.vogella.com/tutorials/AndroidFragments/article.html
The error message is pretty clear. When you use onAttach, the context passed through is actually the host Activity - the biggest boss of all, not the parent fragment. So FragmentB which implement the interface but will not receive any update.
You should use getParentFragment to access FragmentB from FragmentA, then cast it to the Listener interface.
Android best practices for fragment-fragment interaction (described here and here) forces the Activity to implement a listener defined by the child fragment. The Activity then manages the communication between fragments.
From my understanding this is to keep the fragments loosely coupled from each other. However,
Is this also the case for nested fragments? I can imagine it might make sense for a nested fragment to report directly to it's parent fragment instead of the Activity.
If a nested fragment has its parent fragment implement it's listener, how would one (or should one) require the parent fragment to do this. In other words, is a similar to the paradigm to the following but for Fragments:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
If anyone wanted an example of an implementation that ensures parent context implements the callbacks while not caring whether it is an activity or fragment, the following worked for me:
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Callbacks) {
mCallbacks = (Callbacks) context;
} else {
if (getParentFragment() != null && getParentFragment() instanceof Callbacks) {
mCallbacks = (Callbacks) getParentFragment();
} else {
throw new RuntimeException(context.toString()
+ " must implement " + TAG + ".Callbacks");
}
}
}
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
Enjoy!
As long as you define an interface in the fragment, you can have the parent activity or parent fragment implementing it. There is no rule that says fragment should not implement interface of a child fragment. One example where this make sense is that fragment A has two children Fragments B, C. A implements B's interface, when A gets a call back, it might need to update fragment C. Exactly the same thing with activity, just different level.
You can implement the same pattern for child/parent interactions using getParentFragment(). The parent fragment refers to whichever fragment has this one added through its ChildFragmentManager. If this Fragment is attached directly to an Activity, this method returns null.
If I'm inside a Fragment how can I call a parent's activity?
Yes, Its right by calling getActivity and cast it with parent activity to access its methods or variables ((ParentActivityName)getActivity())
Try this one.
ParentActivityName is parent class name
2021 UPDATE
As it's told in the comments, Fragment#onAttach(Activity) is deprecated starting from API 23. Instead:
#Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
// Only attach if the caller context is actually an Activity
if (ctx instanceof Activity) {
mCallback = (OnHeadlineSelectedListener) ctx;
}
} catch (ClassCastException e) {
throw new ClassCastException(ctx.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
ORIGINAL ANSWER
The most proper way is to make your Activity implement an Interface and use listeners. That way the Fragment isn't tied to any specific Activity keeping it reusable. Into the Fragment:
#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 = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
That way, you make the Activity listen to the fragment when it's attached to it.
See also:
http://developer.android.com/training/basics/fragments/communicating.html
Simply call your parent activity using getActivity() method.
CardView cardView = (CardView) getActivity().findView(R.id.your_view);
I have some problems passing data from an activity to fragments in it. I searched around but didn't find an answer which suit my situation well.
I have 2 fragment class named CurrentFragment.java and HistoryFragment.java. I initialize them as tabs in an Activity.
Tab tab = actionBar.newTab()
.setText(R.string.tab_current)
.setTabListener(new TaskitTabListener<CurrentFragment>(
this, "current", CurrentFragment.class));
actionBar.addTab(tab);
tab = actionBar.newTab()
.setText(R.string.tab_history)
.setTabListener(new TaskitTabListener<HistoryFragment>(
this, "history", HistoryFragment.class));
actionBar.addTab(tab);
I was told to use setArguments in the Activity and getArguments in the fragments. But in this situation how do I get fragment objects in the Activity? I can't use getFragmentManager().findFragmentById() since the fragments are added programmatically.
Also, I find some posts saying that I may use getActivity() in fragments to access data in the Activity container, but for me it keep returning null. Does anyone has a working example of that?
[EDIT] I've updated my answer to better respond to your question.
You can also retrieve fragments by tag with getFragmentManager().findFragmentByTag("tag"). Be careful though, if the tab has not been viewed yet the fragment will not exist.
CurrentFragment curFrag = (CurrentFragment)
getFragmentManager().findFragmentByTag("current");
if(curFrag == null) {
// The user hasn't viewed this tab yet
} else {
// Here's your data is a custom function you wrote to receive data as a fragment
curFrag.heresYourData(data)
}
If you want the fragment to pull the data from the activity have your activity implement an Interface defined by the fragment. In the onAttach(Activity activity) lifecycle function for fragments you get access to the activity that created the fragment so you can cast that activity as the Interface you defined and make function calls. To do that put the interface in your fragment like this (You can also make the interface its own file if you want to share the same interface among many fragments):
public interface DataPullingInterface {
public String getData();
}
Then implement the the interface in your activity like this:
public class MyActivity extends Activity implements DataPullingInterface {
// Your activity code here
public String getData() {
return "This is my data"
}
}
Finally in your onAttach(Activity activity) method in CurrentFragment cast the activity you receive as the interface you created so you can call those functions.
private DataPullingInterface mHostInterface;
public void onAttach(Activity activity) {
super.onAttach(activity);
if(D) Log.d(TAG, "onAttach");
try {
mHostInterface = (DataPullingInterface) activity;
} catch(ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement DataPullingInterface");
}
String myData = mHostInterface.getData();
}
I've been working a lot with fragments lately and I was just curious as to what the best practice is for using a reference to a fragment's parent activity. Would it be better to keep calling getActivity() or have a parentActivity variable initialized on the onActivityCreated callback.
This is actually included in the official Android document on Fragments. When you need the context of the parent activity (e.g. Toast, Dialog), you would call getActivity(). When you need to invoke the callback methods in your Fragment's interface, you should use a callback variable that is instantiated in onAttach(...).
public static class FragmentA extends ListFragment {
ExampleFragmentCallbackInterface mListener;
...
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mListener = (ExampleFragmentCallbackInterface ) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement ExampleFragmentCallbackInterface ");
}
}
...
}
Source
getActivity() is best. You need not maintain a variable to store (always, til app cycle!). If needed invoke the method and use! :)
If you are in the fragment which is called from some activity, to get the reference to parent activity you can call it inside onViewCreated() or later hook methods of fragment directly by, it is just to make sure that parent activity is not null
getActivity()
If you want to really make sure you need to check first
if (getActivity() != null){ // then your logic with getActivity()}