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;
Related
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.
I have an activity which hosts three Fragment's and I can switch between Fragment's using BottomNavigationView.The mechanism that I use to switch between Fragment's is using show and hide functions of FragmentTransaction instead of replace function of FragmentTransaction. I am doing so because I want some network operations to be done only once and also to inflate Layout only once.
The problem that I am facing using this mechanism is that when I start another Activity from any of the Fragment and then hit the back button the selectedItem of the BottomNavigationView and the Fragment shown are mismatching.
I was able to solve this problem though but I feel it has less efficiency. The procedure was that whenever I clicked a tab in BottomNavigation while switching Fragment's I gave it some predecided number and saved in a static variable(X) and whenever I clicked back button in the OnResume() method of the hosting activity I made a switch-case block using X to know which Fragment was visible before starting the new Activity and then finally making three FragmentTransaction's to show and hide required Fragment's.
protected void onResume() {
super.onResume();
if(selectedId!=63){
switch(selectedId){
case 0:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_home){handleHomeFragmentVisibility();}
break;
case 1:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_dashboard)
{handleDashboardFragmentVisibility();}
break;
case 2:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_notifications)
{handleNotificationFragmentVisibility();}
break;
}
}
I feel using three FragmentTransaction's is costly and I was looking for some efficient way. Can you tell me one if you know ?
public void handleHomeFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("home") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("home")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
Log.e(TAG,"homeFragmentAdded");
fragmentManager.beginTransaction().add(R.id.container, new HomeFragment(), "home").commit();
}
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("dashboard")).commit();
}
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("requests")).commit();
}
}
public void handleDashboardFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("dashboard")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new DashboardFragment(), "dashboard").commit();
}
if (fragmentManager.findFragmentByTag("home") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("home")).commit();
}
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("requests")).commit();
}
}
public void handleNotificationFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("requests")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new NotificationFragment(), "requests").commit();
}
if (fragmentManager.findFragmentByTag("home") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("home")).commit();
}
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("dashboard")).commit();
}
}
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
FragmentManager fragmentManager = getSupportFragmentManager();
switch (item.getItemId()) {
case R.id.navigation_home:
selectedId=0;
handleHomeFragmentVisibility();
break;
case R.id.navigation_dashboard:
selectedId=1;
handleDashboardFragmentVisibility();
break;
case R.id.navigation_notifications:
selectedId=2;
handleNotificationFragmentVisibility();
break;
}
return true;
}
});
A first note on your code: Avoid boilerplate! Write only one method instead of three and use a signature of the type handleFragmentVisibility(String show, String hide1, String hide2, int container). In case the fragment to be shown is null, instantiate it by testing for show, something like:
Fragment newFragment = (show == "home") ? new HomeFragment() : (show == "dashboard") ? new DashboardFragment() : new NotificationFragment();
However, none of your fragments should ever get null through hiding (please check for yourself), since you don't remove them from your activity or replace them with other fragments. Instead of using show and hide you could also use attach and detach, both sets of methods keep state. I don't see an efficiency problem and you do indeed need to call three FragmentTransactions. It only can be done with less code:
public void handleFragmentVisibility(String show, String hide1, String hide2){
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag(show)).commit();
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(hide1)).commit();
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(hide2)).commit();
}
Please note, that although this method keeps the state of the fragment while hiding or detaching them, other events like orientation change still make it necessary that you take care of saving state in onSaveInstanceState(Bundle savedInstanceState).
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
}
});
The background
I have single activity in my App which loads 2 fragments based on some menu item selection
public class ActivityMain extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
loadFragment(1); // DEFAULT FRAGMENT, AT THE BEGINNING
}
}
..........................
..........................
// This method is called above, ALSO onItemClick in the Navigation Drawer (code not included for brevity)
public void loadFragment(int position) {
Fragment fragment = null;
switch (position) {
case 1:
fragment = new Fragment1();
break;
case 2:
fragment = new Fragment2();
break;
default:
break;
}
if (fragment != null) {
getFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, "frag_" + position).addToBackStack(null).commit();
}
}
}
"Fragment1" is a simple fragment with a fixed text in a TextView. "Fragment2" uses SlidingTabLayout to load a Fragment "FragmentViewPager" in a viewpager using FragmentStatePagerAdapter.
The issue I am facing:
Even if I remove "Fragment2" from the Activity's Frame Layout (using getFragmentManager().beginTransaction().remove), Fragment "FragmentViewPager" does not get destroyed, rather it resumes every time Activity resumes.
Question
Why FragmentViewPager is not destroyed with "Fragment2"?
If you are using FragmentStatePagerAdapter then this will not destroy fragment and when you swipe and come back to that fragment it will show old data without refresh. Because of not destroyed by viewpager.
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);
};
};