I have a fragment which is hosting a TabLayout. I would like to have 2 tabs such that each of those is a fragment. I have been following this guide:
https://guides.codepath.com/android/google-play-style-tabs-using-tablayout
The problem I am trying to solve is to have shared state or a way to communicate between the fragments - my 1st fragment will be hosting a list of Places (from Google place) and my 2nd fragment a map displaying them. My problem is that, when the data from the 1st fragment changes (e.g. filtered) the map fragment should also update its data. Ideally I wouldn't need to recreate the map if no changes have occurred in the list view.
In my FragmentPagerAdapter I have the following method which is retrieving the fragments but I can't figure out a way force fragment update/recreate if the state of the 1st fragment has changed
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
if(position == 0){
fragment = findFragment(PlaceListFragment.getTAG());
if(fragment == null) {
fragment = PlaceListFragment.newInstance(0);
}
return fragment;
}else{
fragment = findFragment(MapViewFragment.getTAG());
if(fragment == null) {
fragment = MapViewFragment.newInstance();
}
return fragment;
}
}
If you want to detect hide/show then just override
public void onHiddenChanged(boolean hidden) {
if(!hidden){
//When fragment is visible
}
Log.i("my_fragment","setUserVisibleHint: "+isVisibleToUser);
}
or
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
//When fragment is visible
}
Log.i("my_fragment","setUserVisibleHint: "+isVisibleToUser);
}
in your fragment.
Related
How can I hide SearchFragment and display ChatFragment, without remove() in ViewPager? I had no issues with creating new instance everytime, but I don't want onDestroy() to have place, since i will be switching these 2 views very frequently. I want to create them once and have the ability to show/hide one for another.
My code is making two views to be seen in fragment_search(id cointainer)at once, then just crashes.
I was thinking about creating both Fragments at start, and then just manipulate them by attach and detach, but I can't really figure this out since ViewPager doesn't make it easier
public Fragment getItem(int position) {
switch (position) {
case 0:
if (fragment1 == null)
{
fragment1 = SearchFragment.newInstance(new SearchFragment.MyListener() {
#Override
public void onChatFound() {
// Dont want to remove fragment, just hide this view || fm.beginTransaction().remove(fragment1).commit();
Fragment chatTag = fm.findFragmentByTag("ChatTag");
if (chatTag == null)
{
chatFragment = ChatFragment.newInstance();
fm.beginTransaction().add(R.id.fragment_search, chatFragment, "ChatTag").commit();
}
fragment1 = chatTag;
notifyDataSetChanged();
}
});
}
return fragment1;
I have one fragment where are three (default) images and when user click on them, they will change to another. But when i swipe to another fragment and back to fragment with images there are not default ones as on the start. When I swipe two times so I will pass to another fragment (distance from original with images is 2 fragments) images are resetted to default. I was trying to implement setOffscreenPageLimit() from ViewPager and set it to 1, but minimum "length" when views in fragments are resetted is 2. How can I change that images to default manually after swipe action? Thank you.
Edit: I think that issue why onResume() is not working here: Fragment onResume not called
but i dont know what that means :/ I have three classes FragmentController.class, PagerAdapter.class and class of specific fragment for example FirstFragment.class. I don't know how connect these classes together.
Check that you create the fragments in the getItem() method of the adapter and do not hold any reference to that fragments (only WeakReferences if necessary), otherwise the fragments could not be destroyed.
EDIT:
The first fragment is unloaded only when you are in the third one because setOffscreenPageLimit is at least 1 so a viewpager allways loads the fragments that are at both sides of the selected one.
What you could do is to update your adapter with this code to provide a getFragment(position) method:
private HashMap<Integer, WeakReference<Fragment>> fragmentReferences = new HashMap<>();
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
case 0:
fragment = FirstFragment.newInstance();
break;
// etc
}
fragmentReferences.put(position, new WeakReference<>(fragment));
return fragment;
}
public Fragment getFragment(int position) {
WeakReference<Fragment> ref = fragmentReferences.get(position);
return ref == null ? null : ref.get();
}
After then you can get the selected fragment and call the method you want from the first fragment when a page is selected:
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int currentPage) {
if (currentPage == 0) {
Fragment firstFragment = adapter.getFragment(0);
if (firstFragment != null) {
// This method resets the images of the first fragment
((FirstFragment) firstFragment).reset();
}
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Do nothing
}
#Override
public void onPageScrollStateChanged(int state) {
// Do nothing
}
});
I have one HomeActivity from where I am calling the list of fragments. In HomeActivity I have sidemenu where all fragments are loaded.
Now,in this list i have one fragment called HomeFragment which display the Google Map with data, using webservice.
What i want is i want to load the Map dat only once(first time) fragment is loaded, not every time when click on sidemenu or come in from any other fragment.
My HomeFragment or any other fragments are loaded at once, not every time.
You can hide/show fragment. No need to replace, remove. For ex, I have 2 fragments FragmentFeed and FragmentNotification, we can hide/show or add fragment.
Handle click menu:
if (tabId.equals(AppConstants.FEED)) {
pushFragments(tabId, getFragmentFeed());
} else if (tabId.equals(AppConstants.NOTIFICATION)) {
pushFragments(tabId, getFragmentNotification());
}
Handle show/hide and add fragment:
public void pushFragments(String tag, Fragment fragment) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if (manager.findFragmentByTag(tag) == null) {
ft.add(R.id.realtabcontent, fragment, tag);
}
Fragment fragmentFeed = manager.findFragmentByTag(TAG_FEED);
Fragment fragmentNoti = manager.findFragmentByTag(TAG_NOTIFICATION);
// Hide all Fragment
if (fragmentFeed != null) {
ft.hide(fragmentFeed);
}
if (fragmentNoti != null) {
ft.hide(fragmentNoti);
}
// Show current Fragment
if (tag == TAG_FEED) {
if (fragmentFeed != null) {
ft.show(fragmentFeed);
}
}
if (tag == TAG_NOTIFICATION) {
if (fragmentNoti != null) {
ft.show(fragmentNoti);
}
}
ft.commitAllowingStateLoss();
}
You should use two things.
First onSavedInstance of your Fragment.
Fill it with the info you want.
Second setRetainState(true). This will prevent your fragment from destroying even if it is detached. Hope this helps.
I have an Android activity that holds and manages six fragments, is fragment is a step in a flow, some of the fragments are replaced and some of them are added.
The Activity just uses a Framelayout as the container for the fragments as follows:
<FrameLayout
android:id="#+id/content"
android:layout_below="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Then the flow of the fragments is like this:
//Activity starts, add first Fragment
fragmentManager.beginTransaction().replace(R.id.content, FirstFragment.newInstance(listOfItems)).commit();
then
//User pressed button, activity got callback from first fragment
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.content, fragment2);
transaction.addToBackStack("frag2");
transaction.commit();
then
//Another callback from Frag2, perform the add of frag 3
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.content, fragment3);
transaction.addToBackStack("frag3");
transaction.commit();
And so on....
I also manage the back stack from the Activity like this:
//Controlling the back stack when the user selects the soft back button in the toolbar
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (fragmentManager.getBackStackEntryCount() == 0) {
super.onBackPressed();
overridePendingTransition(R.anim.no_change, R.anim.slide_down);
} else {
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
Fragment fragment = fragmentManager.getFragments()
.get(fragmentManager.getBackStackEntryCount());
fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
}
}
return true;
}
return super.onOptionsItemSelected(item);
}
//Controlling the back stack when the user selects the "hardware" back button
#Override
public void onBackPressed() {
if (fragmentManager.getBackStackEntryCount() == 0) {
super.onBackPressed();
overridePendingTransition(R.anim.no_change, R.anim.slide_down);
} else {
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
Fragment fragment = fragmentManager.getFragments()
.get(fragmentManager.getBackStackEntryCount());
fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
}
}
}
My problem is that I open the app and go to this Activity which loads the fragments and then go through the flow to a certain stage ( I haven't narrowed it down yet) then I press the home button and blank my screen. Now after a certain amount of time when I open the app again it opens on the fragment I left but everything seems to be messed up, when I press back it seems to pop the wrong fragment and the UI becomes mixed up with the different fragments.
My guess is that when I open the app again the Activity onResume or the Fragment onResume or some lifecycle event is being called that I am not handling correctly?
So I was wondering is there best practices, guidelines or patterns that should be adhered to when using a Fragment pattern like I am doing so?
Since you have so many fragments in one activity, and they use the same container, that means all fragments are in the same place, and only one fragment will show at a time.
So why don't you use ViewPager and let FragmentPagerAdapter manager these fragments? In this way, you do not need to manager fragment lifecycle by yourself, you just need to override FragmentPagerAdapter methods:
to create fragment instance by getItem,
to update fragment by getItemPosition and Adapter.notifyDataSetChanged(),
to show selected fragment by mViewPager.setCurrentItem(i)
Code snippets, detail refer to https://github.com/li2/Update_Replace_Fragment_In_ViewPager/
private FragmentPagerAdapter mViewPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
#Override
public int getCount() {
return PAGE_COUNT;
}
// Return the Fragment associated with a specified position.
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem(" + position + ")");
if (position == 0) {
return Page0Fragment.newInstance(mDate);
} else if (position == 1) {
return Page1Fragment.newInstance(mContent);
}
return null;
}
#Override
// To update fragment in ViewPager, we should override getItemPosition() method,
// in this method, we call the fragment's public updating method.
public int getItemPosition(Object object) {
Log.d(TAG, "getItemPosition(" + object.getClass().getSimpleName() + ")");
if (object instanceof Page0Fragment) {
((Page0Fragment) object).updateDate(mDate);
} else if (object instanceof Page1Fragment) {
((Page1Fragment) object).updateContent(mContent);
}
return super.getItemPosition(object);
};
};
What I need is exactly an onResume method (as it works for activities) for a specific fragment. I'm adding the fragment (let's say fragment A) to the back stack, and opening another fragment (fragment B) (again adding to back stack) from fragment A. I want to update toolbar when fragment B is closed and fragment A is on screen again. I expect onCreateView to get called but it's not getting called when I pop fragment B. I also tried adding an OnBackStackChangedListener to fragment A but then I cannot track which fragment is on the screen when the back stack changes.
So my question is how to make onCreateView get called when I turn back to fragment A. And if this is not a good practice, how else can I track this event?
Edit
I'm showing new fragments with this code:
getSupportFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.addToBackStack(tag)
.commit();
Should I change it somehow to make onCreateView get called? Since I'm adding new fragment B on existing fragment A (I can even click on a button which is in fragment A when B is on the screen), when I pop fragment B, nothing changes with fragment A's situation.
Override this method in the Fragment and check the boolean value
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//Log.e("setUserVisibleHint", "isVisibleToUser " + isVisibleToUser);
}
Put the code that you need to be executed whenever the fragment becomes visible/is hidden in this method, according to the isVisibleToUser boolean value
Did you try OnBackStackChangedListener this way?
public class BlankFragment2 extends Fragment {
public BlankFragment2() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getFragmentManager()==null)
return;
Fragment fr = getFragmentManager().findFragmentById(R.id.container)//id of your container;
if (fr instanceof BlankFragment2) {
//On resume code goes here
}
}
});
return inflater.inflate(R.layout.fragment_blank_fragment2, container, false);
}
}
I hope this solution will works.
1) Put/call addOnBackStackChangedListener on your Activity
getSupportFragmentManager().addOnBackStackChangedListener(backStacklistener);
2) Define backStacklistener inside your Activity
FragmentManager.OnBackStackChangedListener backStacklistener = new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager manager = getSupportFragmentManager();
if (manager != null) {
Fragment fragment = manager.findFragmentById(R.id.fragment);
if(fragment instanceof OutboxFragment) {
OutboxFragment currFrag = (OutboxFragment) fragment;
currFrag.onFragmentResume();
}
}
}
};
3) Provide a method on your fragment that you want to be triggered. In this case I create method named onFragmentResume()
public void onFragmentResume() {
MainActivity activity = (MainActivity) getActivity();
activity.showFab();
// or do another thing here
}
Good luck!