This is a design question, rather than a technical one.
General case: I want an UI event in a Fragment to make Activity-wide changes.
Specific case: I have two fragments, hosted in the same activity. When the user clicks a button in one of those fragments, I want it to be replaced by the other.
I don't want, however, my Fragments touching my activity. I may want to change the behavior later (maybe, in a bigger screen, show both fragments instead of replacing the first), and I don't want my Fragment code to have that logic.
What I did was implement a Listener class in my fragments, that reports events back to the Activity. This way, if I want to use another Activity class with different display behavior, I can just change the listener and leave the Fragment code untouched.
Is this a good way to go about it? Is there a standard good practice, or a better design pattern?
Using listeners is the recommended way of communicating between Fragment and your activity.
See this Android documentatin section for infromation. Long story short they just implement a listener interface by the Activity class and cast getActivity() result in a fragment to a listener.
From my personal experience this is very convenient because lets you to:
Easilly switch underlying activity (e.g. you host entire fragment in a wrapper activity for compatibility in pre-3.0 and host this fragment along with others in 11+)
Easilly control if the wrapper activity supports callbacks or not. Just check is it does implement the listener and do your app specific actions if it doesn't.
You are right on about using a Listener. This is something I also had to deal with in a project at work. The best way to handle it is to make the Fragment stand-alone in nature. Anything wishing to interact with the Fragment should use its public API and/or set listeners for specific events. If you are familiar with Design Patterns, this is the Observer pattern. The events can be general or specific as well as contain data or no data.
As an example of my project, I had two Fragments. A ListFragment and an InfoFragment that displayed the selected ListItem. The ListFragment already has a Listener interface for my Activity to hook into, but the InfoFragment does not since its your basic Fragment. I added a Listener interface to the InfoFragment that would be notified when the Fragment wanted to close. For the Fragment, this could be by a button press, or specific action occured, but as far as my Activity is concerned, when the Event is triggered, it would close up the Fragment view.
Don't be afraid to use a lot of Listeners for Fragments, but also try to group them by a specific action using data parameters to individualize them. Hope this helps!
A technical answer for:
I have two fragments, hosted in the same activity. When the user clicks a button in one of those fragments, I want it to be replaced by the other.
FragmentTransaction ft = this.getFragmentManager().beginTransaction();
Fragment mFragment = Fragment.instantiate(this.Activity(), Fragment2.class.getName());
ft.replace(android.R.id.content, mFragment);
ft.commit();
public class Example_3_Mainfile extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.example_3_mainfile);
Fragment fr ;//make class that extend to thefragment
fr = new Act_2_1();
FragmentManager fm = getFragmentManager();
FragmentTransaction fragmentTransaction = fm.beginTransaction();
fragmentTransaction.replace(R.id.fragment_place, fr);
//id get of fragment tag from xml file there decelar
fragmentTransaction.commit();
}
}
Related
how to make button to open another fragment. being within a fragment. kotlin
I'm starting in kotlin and I'm having a hard time trying to open a fragment with a button, how do I?
You need to use FragmentManager and FragmentTransaction to add your fragment on the fly. you can call a function similar to this in your button's onClick method. But it is recommended for the parent activity to handle each fragment's lifecycle and the fragments
are not supposed to interact each other. The following is taken from the developer docs, that can be found here.
"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 either through a shared ViewModel or through the associated Activity. Two Fragments should never communicate directly."
fun createFragmentonTheFly(){
var mFragmentTransaction: FragmentTransaction = getSupportFragmentManager().beginTransaction()
mFragmentTransaction.add(R.id.fr_container,new ProductListFragment())
mFragmentTransaction.commit()
}
The best way to do it would be to add an interface let say onFragmentDetachedLisetner and add one method replaceFragment() or something and make your Activity implement this interaface and had it replace as soon as the fragment is detached and make your fragment that contains your button finish itself when user clicks the button, then your activity will replace it with the one you wanted to start. And also consider reusing fragments, as that is the main purpose of fragments at the first place.
I have an Activity that creates a Fragment and then this Fragment creates another Fragment:
Activity -> Fragment1 -> Fragment2
I am now in Fragment2 and I'd like to go back to Fragment1 by clicking on a button.
In my OnClickListener of my button I have:
getActivity().getSupportFragmentManager().beginTransaction().remove(fragment2.this).commit();
This brings me to the Activity. Is there actually a way to just remove Fragment2 and go to Fragment1?
Thank you in advance for your time, I could not find any suitable info online!
#AhmedAbidi has a nice insight to your problem and yes, implementing popBackStack properly may solve your problem. But anyway, I would like to suggest a different approach to handle this type of situations.
Write two public functions in your Activity to switch between your fragments.
public void switchToFragment1() {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, new Fragment1()).commit();
}
public void switchToFragment2() {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, new Fragment2()).commit();
}
Now from the button click in your Fragment1 you might launch the Fragment2 via,
((YourActivity) getActivity()).switchToFragment2();
And the same thing while switching to Fragment1
((YourActivity) getActivity()).switchToFragment1();
Your question needs more code for clarity.
Is Fragment1 adding Fragment2 via getSupportFragmentManager() or getChildFragmentManager()? Presumably it appears you are doing the former, and if so, you are incorrectly using the Fragment API. Fragments are not supposed to know about each other, per Android's Fragment Documentation:
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.
You should therefore implement the appropriate listeners to communicate from Fragment1 to Activity, which can then decide where/when to add Fragment2 -- then properly utilize the back stack functionality of getSupportFragmentManager().addToBackStack()... with getSupportFragmentManager().popBackStack(). See Android Documentation on Back Navigation for Fragments for further explanation.
Is there a way to know which Fragment is currently displayed in a given <fragment> container of an Activity without keeping track of all the changes via the onAttachFragment callback?
Is it even possible to know which fragments are displayed when fragment transactions can take place when the user presses the back key? In this latter case, i.e. when a Fragment is re-displayed due to a back, the onAttach is not called.
In my experience, the only way to know for sure which fragment is being displayed is to keep track of that carefully yourself.
For example, you could make a variable in your Activity:
Fragment mCurrentDisplayedFragment;
and then whenever the user requests a different fragment do:
mCurrentFragment = (Fragment) userRequestedFragment;
fragmentManager.replace(container, mCurrentFragment, tag);
Then, whenever you needed to do things to the currently displayed fragment, you could triage it by try/catching a cast or with instanceof.
You could also handle the back pressed behavior by overriding that method in the activity:
#Override
public void onBackPressed() {
int stackSize = fragmentManager.getBackStackEntryCount();
// This counts up from the bottom so the most recent fragment is the biggest number/size
backFragId = fragmentManager.getBackStackEntryAt(stackSize);
// Get a handle on the fragment that is about to be popped
mCurrentFragment = fragmentManager.findFragmentById(backFragId);
super.onBackPressed();
}
Also, are you sure that onAttach is not called when a fragment is popped off the stack? I seem to remember that it will be, and you can call through the interface created there (if you have one and the activity implements it) to register the fragment as the current fragment in the activity at the time.
But to directly answer your question, there isn't a built in way to just know what fragment is currently displayed (and there could be more than one!). The implementation details of that are up to you. Hopefully I've given you some ideas of how it could be handled though. You might also find the FragmentManager documentation helpful.
Each time when you add/replace fragment to the container, use tag for it:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.container, fragment, tag).commit();
then you can find out the fragment is current visible or not:
Fragment fg = getFragmentManger().findFragmentByTag(tag);
if(fg.isVisible())
//fg is the current visible fragment
Hope this help!
Just a general question about working with Fragments and Activitys for android development: where does the business end of the functional code go for Fragments loaded into an Activity dynamically? (i.e. a fragment's OnClickListeners, OnCheckedChangedListeners, button logic methods...)
Do they go in the Fragment class, or the Activity class?
All the GUI logic for views attached to a fragment should be contained inside the fragment itself.
Thus a fragment should be as self contained as possible.
You can, though, if necessary do callbacks to your activity based on fragment GUI interaction. This can easily be done like this inside the fragment:
#Override
public void onAttach(Activity activity) {
if (!(activity instanceof SherlockFragmentActivity)) {
throw new IllegalStateException(getClass().getSimpleName()
+ " must be attached to a SherlockFragmentActivity.");
}
mActivity = (SherlockFragmentActivity) activity;
super.onAttach(activity);
}
In this specific case the reason for gaining a reference to SherlockFragmentActivity is to gain access to the support menu inflater mActivity.getSupportMenuInflater(), hence the construction can of course also serve to gain information from the underlying activity.
This probably depends on how much the Fragment's functionalities have in common, and how many, let's say Buttons, have to be handled.
I personally (and it's probably most common practice) handle onClick(...) events separately for each Fragment, meaning that I let each Fragment implement it's own OnClickListener.
Furthermore, when handling everything through the Activity, probably not all the components that react to click-events are in memory at all times and can be reached via findViewById(...), depending on which Fragment is currently displayed and how your user-interface is built up in general.
they always in fragment class because fragment is one type of component in android which we can reuse it. if we put onclick and oncheckchanged in activity then what meaning of reusing that component??
for more information about please go through following step:
Link 1 for basic level of information about fragment and how to handle them
Link 2 for dealing with multi pane fragment
Standard site for fragment
It depends:
If fragment can handle logic which is self sufficient(complete) then that code can be handled by fragment. e.g. on click call phone number.
If fragment have UI whose action is activity specific, then you want to add listener in activity.
e.g. master detail view like email client, on tablet user click on title fragment1 which have list of email titles, then handler on click in activity can show detail fragment2 in activity.
In all you want to keep fragment reusable.
I have a main TabActivity which has two tabs, A and B (for now). Tab A loads a FragmentActivity (code given below) which just conatains a FrameLayout, so I can load my Fragments for that specific Tab in it.
The first Fragment has some TextViews and one ListView. Data is pulled from a web service. When I click on an ListView's item, I load that item's detail in another Fragment (this also comes from a web service) and replace the current Fragment (with ListView and other controls) with another detail fragment.
To achieve this, I am using android-support-v4.jar library to use Fragments as they were preferred.
Tab A's FragmentActivity's XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<FrameLayout
android:id="#+id/updates_frame"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/background"/>
</LinearLayout>
Tab A's FragmentActivity Java code:
public class UpdatesFragmentActivity extends FragmentActivity implements
IUpdateNotifier {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.updates);
//Load Initial Fragment into FrameLayout
//I am adding this Fragment to BackStack
Fragment newFragment = new UpdatesFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.addToBackStack(null);
ft.add(R.id.updates_frame, newFragment);
ft.commit();
}
//This is an Interface method which I call with the clicked "FEED" object to load its detail in another Fragment
#Override
public void onFeedSelected(Feed feed) {
// Instantiate a new fragment.
Fragment newFragment = new FeedDetailFragment(feed);
// Add the fragment to the activity, pushing this transaction
// on to the back stack.
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.updates_frame, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
//This is another Interface Method which I call when the user presses "BACK".
//I am trying to load the previously loaded Fragment, which I added to BackStack.
//But this causes reconstruction of the previously loaded fragment. LIST in this case
//which call the web service. I DONT WANT TO CALL SERVICE AGAIN.
#Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
}
}
}
I have created an interface IUpdateNotifier, which contains two methods:
public void onFeedSelected(Feed feed);
public void onBackPressed();
Parent UpdatesFragmentActivity implements these methods. I call these methods from children Fragments upon following actions.
I call onFeedSelected(Feed feed) from the Fragment which has a ListView. I send the clicked feed item to parent FragmentActivity, so it loads another Fragment which would contain that feed detail.
I call onBackPressed() from the second feed detail Fragment when the user presses a button that is supposed to bring back the first fragment which contained ListView with other controls. As you can see, I try to call FragmentManager's popBackStack() method to bring back that first Fragment...
But the first Fragment gets refreshed and loads all the data from web service.
Actually I cannot get and store data only once nor the updates are frequent on some time intervals. The user can update the list when he wants. Initially, the list loads the top 10 items from the service, and then user can click the "More" button at the end of list if he wants to load more items.
It will load the next 10 items and so on. But I think I can store the retrieved ArrayList in some variable in UpdatesFragmentActivity and then just reassign that ArrayList to the list's adapter instead of loading the data from service, but I don't know how to make Fragment not to call service again.
I want it to behave like when I click on tab 2 and then on tab 1 again. It simply shows the loaded data as if was hidden and does not call the service.
How can I achieve this?
Your design pattern is flawed due to a poor separation of concerns. The updating of data should be decoupled from the UI, therfore when a user goes back to the previous Fragment it should have nothing to do with loading data from a web service.
There are a couple of easy fixes but I do not know what will work best as you have given little context to the problem.
First option would be to introduce a Splash Screen on start up. This Activity would make use of an AsyncTask to download the data you need from the web service. This works well if you only want the data to be downloaded once during the runtime of the app. You would make sure not to add this Activity to the history so when back is pressed from the next activity, the app would then exit.
Another option, which I have used in many apps and the one I prefer, is the use of Alarms via the AlarmManager. You can set a periodic updates at specific time intervals, the AlarmManager even helps you to the point where it contains enumerations of time. The Alarm will trigger a broadcast receiver which will execute your custom code, that will download the data you need from the web service and store it.
There is a tutorial on this approach, which can be found here http://android.arnodenhond.com/tutorials/alarm-notification.
Finally; you should not need to pop the back stack to get around this problem, although you might be doing this for entirely different reasons but it is hard to tell without more info.
Your question is not clear enough, ask more simple and precise questions... And as you say above
popBackStack does not load the last fragment, it's commonly used to pop the entire stack :
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
before loading another fragment
beginTransaction()
replace() Or add()
commit()
That's all i can think about with that question
Good luck