Is there any standard pattern for communication between two fragments where one fragment hosts another fragment? This article Communicating with Other Fragments explains how to communicate between two (independent?) fragments where Activity is communication mediator. However, this "pattern" does not suit my case.
I have created fragment which contains ViewPager. ViewPager's items, fragments, produces some events which need to be processed in hosting fragment. Since hosting fragment should be a standalone component, I would like to ommit Activity from the communication process.
What is the best place for registering of listeners in such case?
Our solution was to generalize the pattern you linked to from "communicate with my activity" to "communicate with my parent", which could be an Activity or another Fragment.
Instead of always expecting the Activity to implement an interface and casting it in onAttach, we have a utility method that looks for a valid callback for the current fragment and uses that at the point where we want to invoke the callback.
/**
* #param fragment The fragment to get the listener for.
* #param listenerClass The class of the listener to get.
* #param <T> Type of the listener to get.
* #return A listener object for the given fragment, cast from the owning parent fragment or
* Activity, or null if neither is a listener.
*/
#Nullable
public static <T> T getListener(#NonNull Fragment fragment, #NonNull Class<T> listenerClass) {
T listener = null;
if (listenerClass.isInstance(fragment.getParentFragment())) {
listener = listenerClass.cast(fragment.getParentFragment());
}
else if (listenerClass.isInstance(fragment.getActivity())) {
listener = listenerClass.cast(fragment.getActivity());
}
return listener;
}
This says: given some fragment and an interface callback type, does my owning fragment implement that interface? If so use that. If not, does my Activity implement the interface? If so, use that. Essentially, we walk up the hierarchy looking for a handler.
So in your child fragment:
private void invokeListener() {
Listener listener = getListener(this, Listener.class);
if (listener != null) {
listener.onThingHappened();
}
}
Then in your parent fragment:
public class ParentFragment extends Fragment implements Listener {
#Override
public void onThingHappened() {
// Handle view pager fragment event
}
}
If you later create an instance of the child and add it to an Activity, just have that Activity implements the callback, and it just works™.
Hope that helps!
Related
I have implemented a ViewPager with FragmentStatePagerAdapter and i want an ItemClickListener so that i can open the detail screen when the user clicks on each fragment of the viewpager. I am not able to find any such methods which are available.
You should implement a callback interface to communicate between your fragments and your containing activity. In each fragment, set your needed listeners, and then pass any information needed back to your activity to handle the click.
public class MyFragment extends Fragment {
public interface MyFragmentListener {
void onTheActivityICareAbout();
}
private MyFragmentListener mCallback;
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mCallback = (MyFragmentListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement MyFragmentListener");
}
}
}
Then when you bind your view, set your listner to use the callback on whatever event you need and your activity has your centralized code to handle that. Any interface method can also be used to pass data back to the activity as well.
You can see this link for more details.
Your FragmentStatePageAdapter will have a method that looks like this:
override fun getItem(day: Int): Fragment = YourPageFrament.newInstance()
So, in the code for YourPageFragment, you can set on OnClickListener on the fragment itself.
You can set this listener by modifying the XML layout file for that fragment to include a Linear Layout or some kind of layout.
Then, in your YourPageFragment class, possibly in your onCreateView() method, you can set on OnClickListener on the fragment's layout.
I have two fragments, A and B let's say, where B contains a list. I would like to add a listener on Fragment B that notifies Fragment A of the chosen list item. I couldn't figure out how to initialize the listener in Fragment B since it is bad practice to pass arguments in fragment's constructors.
NOTE: Fragment B is contained inside Fragment A. i.e. I have a FrameLayout in Fragment A; and Fragment B covers that FrameLayout.
Any idea how I could do that?
If you're saying that Fragment B is a child fragment of Fragment A (that is, you've added it to Fragment A using Fragment A's getChildFragmentManager()), then you can use the same approach that you use for Activity interfaces, but using getParentFragment() instead of getActivity().
For example:
Fragment B:
#Override
public void onAttach(Context context) {
MyInterface myInterface = (MyInterface) getParentFragment();
}
Assuming that Fragment A implements MyInterface.
One convenience method we've used to avoid having to know whether a Fragment is hosted by another Fragment or an Activity is something like:
public static <T> getInterface(Class<T> interfaceClass, Fragment thisFragment) {
final Fragment parent = thisFragment.getParentFragment();
if (parent != null && interfaceClass.isAssignableFrom(parent)) {
return interfaceClass.cast(parent);
}
final Activity activity = thisFragment.getActivity();
if (activity != null && interfaceClass.isAssignableFrom(activity)) {
return interfaceClass.cast(activity);
}
return null;
}
Then you can just use:
MyInterface myInterface = getInterface(MyInterface.class, this);
and it doesn't matter whether Fragment B is hosted as a child Fragment or in an Activity directly.
A better approach for this situation, since what you want to do is communication between fragments, is to use an interface. You want to notify A when B has changed. This should be done through the parent activity. Here is the android documentation on the topic: https://developer.android.com/training/basics/fragments/communicating.html.
The gist of it is that you want to define an interface with a method called OnItemSelected (you can name it whatever you want). In B, you want a reference to this interface. When an item is selected, call your new OnItemSelected method. Implement this interface in the parent activity of the two fragments. In the implementation, you can put whatever code you want to modify A.
An example
CommunicationInterface
public interface CommunicationInterface {
public void onItemSelected(int position);
}
FragmentB
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
CommunicationInterface myInterface = (CommunicationInterface) getActivity();
// What ever else you want here
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Send the event to the host activity
myInterface.onItemSelected(position);
}
MainActivity
public class MainActivity extends FragmentActivity implements CommunicationInterface {
// What ever other code you have
#Override
public void onItemSelected(int position) {
FragmentA fragA = (FragmentA)
getSupportFragmentManager().findFragmentById(R.id.fragment_a);
// Code to interact with Fragment A
}
Checkout the contract pattern https://gist.github.com/JakeWharton/2621173
If you are using multiple fragment, you dont have do it for every fragment, just add it to your BaseActivity if you have one.
This example shows the communication between activity and fragment. But for nested fragment you can replace the acitivy with getParentFragment();
In my android application, I have two Fragments. Parent Fragment contains list of available Filter Types and when a particular Filter Type is clicked (in Parent Fragment - Yellow Background) corresponding Child Fragment (Pink Background) opens with list of available options for selected filter type. My requirement is once User select/deselect an option in child fragment, it should reflect/update option count (Green color) in parent Fragment.
Please check attached wireframe.
You can use Otto Bus for comunications between Fragments, fragments-activities, services, etc.
Maybe, the first time you can be a little weird if you have not used before but it is very powerful and very easy to use. You can find the library and a tutorial here:
http://square.github.io/otto/
An example. In your adapter or where you have your item click event you cand send a Object by the Bus.
In your bus you invoque the post method and pass the object. (I recommended create a singleton for Bus).
The singleton Bus Provider.
/**
* Canal de comunicacion
*/
public class BusProvider {
private static final Bus REST_BUS = new Bus(ThreadEnforcer.ANY);
private static final Bus UI_BUS = new Bus();
private BusProvider() {};
public static Bus getRestBusInstance() {
return REST_BUS;
}
public static Bus getUIBusInstance () {
return UI_BUS;
}
}
You send a Object in the bus (in your child fragment) like this:
BusProvider.getUIBusInstance().post(itemSelected);
And in your parent fragment you subscribe for this event:
#Subscribe
public void activitySelected(final Item itemSelected) {
}
Hope it helps you!!
Even though my answer might be late, I guess it still could help :
The solution is simple, in case you need to access a parent fragment from a child one, then use a specific tag for the parent when adding it to the stack, like in the following code example :
1) In the containing activity :
// We instanciate the parent fragment
ParentFragment parentFragment = new ParentFragment();
FragmentTransaction ft = fm.beginTransaction();
// We add the parent fragment with a tag, so we can use it later to fetch
// the current instance of the parent fragment from the child fragment
ft.replace(R.id.content_frame, parentFragment, "parent_fragment_tag");
ft.addToBackStack(null);
// Commit transaction
ft.commit();
2) In the child fragment, we get the current parent fragment instance like this :
Fragment parentFragment = getFragmentManager().findFragmentByTag("parent_fragment_tag");
I hope this answer can be of help.
any android developer here? i need to ask something regarding communication between different fragment like i select something from list of fragment one and on that even fragment two is replaced with another layout?
i have listview adapter in fragment 1 and its view in fragment two.
in fragment one i have already implement onClickListener and switch by getting the position from clicklistener method. i used fragment manager but not getting the way i want.
please reply with code and explanation!
any help will be appreciated!
If I understand your question, what you're trying to implement is a "master-detail" flow. Let us call your first Fragment the list Fragment and your second Fragment the detail Fragment and let us assume that they both exist within a parent Activity.
To establish communication between your list Fragment and your detail Fragment, you could define a callback interface that the parent activity of both Fragments implement. So, in your list Fragment, you would have:
public class MyListFragment extends ListFragment {
// Create a reference to your callback
private ListItemSelectedListener mCallback;
// Define your callback interface
public interface ListItemSelectedListener {
onListItemSelected(int position, Object data);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Send the event to the parent activity
mCallback.onListItemSelected(position, getData(position));
}
}
Then, your parent activity implements the callback listener and triggers a change in the detail Fragment when necessary.
public class MasterDetailActivity extends Activity
implements MyListFragment.ListItemSelectedListener {
//....
// Implement the callback method defined in the interface
public void onListItemSelected(int position, Object data) {
// Get a reference to your detail Fragment
DetailFragment detailFragment = (DetailFragment)
getFragmentManager().findFragmentById(R.id.detail_fragment);
/* This is assuming both the list fragment and the detail fragment
* exist within the parent Activity
*/
if (detailFragment != null) {
detailFragment.changeDetailView(position, data);
}
}
}
Check out Google's document on Fragment communication to learn more.
I'm using a master/detail pattern with one activity managing the 2-pane view and the selector list and the other activity managing the detail fragments. I'm using an interface to handle fragment callbacks to the Activities.
There seems to be a lot of code duplication though with the details activity copying many of the callback methods from the 2-pane activity. Where appropriate, I've used static methods where context isn't required, but where context is required I'm not sure how to remove the code duplication neatly.
Inheritance with an Abstract parent Activity is an option but seems like a lot of overhead.
Is there a better way of doing this?
I asked a similar question here: How many Activities vs Fragments?
I too was worried about the duplication of logic, and the answers I got caused quite a healthy debate.
In the end I chose to follow Stephen's answer, of putting as much of the logic into the Fragments themselves.
However other contributors seemed very keen on duplicating the logic as per the examples.
So lets say u have Activity AB that controls Frag A and Fragment B.
MY ANSWER:
If the variable is used by Frag A and Frag B, put it in Activity AB. Then pass it to Frag A or Frag B everything they need it. Or have Frag A or Frag B retrieve it from Activity AB.
If the variable is used by Frag A only or Frag B only, put it in Frag A or Frag B respectively.
For methods that are used by both Frag A and Frag B, put those methods in another class and create instances of that class inside Frag A and Frag B for each of the 2 fragments to use.
The following is an answer I gave to another person. However, it seems relevant to your question so I am re-posting it here.
Inside Fragment A u need an interface that Activity AB can implement.
In the sample android code, they have:
private Callbacks mCallbacks = sDummyCallbacks;
/*A callback interface that all activities containing this fragment must implement. This mechanism allows activities to be notified of item selections.
*/
public interface Callbacks {
/*Callback for when an item has been selected. */
public void onItemSelected(String id);
}
/*A dummy implementation of the {#link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity. */
private static Callbacks sDummyCallbacks = new Callbacks() {
#Override
public void onItemSelected(String id) {
}
};
The Callback interface is put inside one of your Fragments (let’s say Fragment A). I think the purpose of this Callbacks interface is like a nested class inside Frag A which any Activity can implement. So if Fragment A was a TV, the CallBacks is the TV Remote (interface) that allows Fragment A to be used by Activity AB. I may be wrong about the details because I'm a noob but I did get my program to work perfectly on all screen sizes and this is what I used.
So inside Fragment A, we have:
(I took this from Android’s Sample programs)
#Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
//mCallbacks.onItemSelected( PUT YOUR SHIT HERE. int, String, etc.);
//mCallbacks.onItemSelected (Object);
}
And inside Activity AB we override the onItemSelected method:
public class AB extends FragmentActivity implements ItemListFragment.Callbacks {
//...
#Override
//public void onItemSelected (CATCH YOUR SHIT HERE) {
//public void onItemSelected (Object obj) {
public void onItemSelected(String id) {
//Pass Data to Fragment B. For example:
Bundle arguments = new Bundle();
arguments.putString(“FragmentB_package”, id);
FragmentB fragment = new FragmentB();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction().replace(R.id.item_detail_container, fragment).commit();
}
So inside Activity AB, you basically throwing everything into a Bundle and passing it to B. If u are not sure how to use a Bundle, look the class up.
I am basically going by the sample code that Android provided. The one with the DummyContent stuff. When u make a new Android Application Package, it's the one titled MasterDetailFlow.