I have 3 Fragments inside my ViewPager.
Inside my Fragments I added static ToggleButtons.(static, because I didn't find a workaround for a nullpointerexception, if I wanted to use findViewById() inside my Fragment classes in custom methods and not in onCreateView() )
Outside the ViewPager in my MainActivity I have a Reset-Button, that should reset all ToggleButtons (on all 3 Fragments) to "unchecked".
Every time i click on the Reset-Button, only the current Page and every neighbor-page gets updated.
E.G.
Current Page : 0 Updated Pages: 0,1
Current Page : 1 Updated Pages: 0,1,2
Current Page : 2 Updated Pages: 1,2
I think the Problem is the FragmentPagerAdapter. The Documentation says :
Implementation of PagerAdapter that represents each page as a Fragment
that is persistently kept in the fragment manager as long as the user
can return to the page.
When I'm on Page 2 i can not directly return to Page 0 , so I think the Views from Page 0 (= Fragment 0 ) are not in memory anymore?!
So how can I access the ToggleButtons inside a Fragment that is not visible at the moment nor is a neighbor of the current Fragment ? Is there any workaround?
EDIT:
I found out, that in deed all checked()-values of my ToggleButtons (on ALL Fragments) get updated, but not the inflated Views of Fragments, that are not visible and not neighbor Fragments. So when I return to those previous Fragments, the checked()-values of the ToggleButtons are reset to the state last time the fragment was visible. strange...
Example:
Page2 active - ToggleButton_Page2.checked() = true
switch to Page 0
press Reset-Button. Toast ToggleButton_Page2.checked() = false
switch back to Page 2 (or already at Page 1)
ToggleButton_Page2.checked() = true //it should be false
SOURCE:
Example Source Code
So how can I access the ToggleButtons inside a Fragment that is not
visible at the moment nor is a neighbor of the current Fragment ? Is
there any workaround?
You have a lot of problems in your code, you should read a bit more about Fragments and using them in a ViewPager.
In the reset and status buttons OnClickListeners listeners you write _adapter.getItem(x); in an attempt to get a handle to the Fragment representing the page at that position. This will not work as simply calling that method will return a new instance of the Fragment at that position and not the actual Fragment the ViewPager used(by calling getItem() at previous moment). That new instance you get after calling getItem() is not tied to the Activity and its onCreateView method wasn't called so it has no view(and you get the NullPointerException when you access it). You then tried to get around this by making the ToggelButton as a static field in the Fragment which will work as the field will be initialized when the ViewPager properly creates the Fragments at start. But you shouldn't do this, static fields that hold references to the Context(like any View does) are dangerous as you risk leaking that Context.
Also, related to what I said above, you don't need to pass a Context to a Fragment as a Fragment which is tied to an Activity has the getActivity() method which returns a reference to that Activity.
You shouldn't access any fragments from the ViewPager which aren't near to the visible fragment(one on the left/right of the visible position, unless you don't play with the setOffscreenPageLimit() method of the ViewPager). The ViewPager has a mechanism to improve performance(like a ListView does) so it only creates what it needs immediately so it can provide a smooth swipe for the user.
I've made some changes to your project, check them out. For further questions please post the relevant code in your question.
Salut,
Thank you for your good information, and the improved code!
"But you shouldn't do this, static fields that hold references to the
Context(like any View does) are dangerous as you risk leaking that
Context."
I'm new to Fragment implementation, and spent hours of finding out how the input String is correctly used for findFragmentByTag().(Now knowing how to use it I never thought that it would be that complex). So I decided to do that static workaround, which wasn't a good idea...
As I understand you, i CAN access more then +-1 Fragments, for example if I use setOffscreenPageLimit(3). I think this is the answer to my question.
Related
In my app I have a ViewPager2, the fragment inside this ViewPager have some code on onViewCreated. I have 2 question:
If I update the dataset and then return to old one the onViewCreated is not called, because of fragment cache, how can I intercept the re-show? I am ok with cache, bui I want restore the initial focus of the fragment when re-showed.
How can I intercept the hiding from the fragment? My fragment show a dialog, then an external event can change the data set, I want in this case dismiss the dialog.
Are 2 days that I am trying to fix those 2 issues.
EDIT: I solved the 2 observing the changing of data set.
Data sharing by ViewModel looks like a good solution for both cases. Fragments will rely on data change in shared ViewModel (depends on the structure of your screen it can be data sharing with host activity or parent fragment).
If it's not what you are looking for, provide more details.
I have an Activity that contains a ViewPager with four tabs .
each tab has a fragment.
When I click something in tab 4, I want tab 3 to refresh(or access a non-static method in tab 3 ) ..I've searched but all I found had this in it:
FragmentB fragment = (FragmentB)getFragmentManager().findFragmentByTag("FragmentB");
and I can't use it since I never set a tag on the fragment.
I also could call a function from fragment in another fragment but it has to be static , and I don't want that since everything inside it must be static too and that would mess things up for me..
is there a solution to this ?
First of all i think it's not a good practice to update a view which user cannot see on screen at the moment. It's much better to update your data when user navigates to screen. By the way it not our first concern now.
I'm not sure this is still a valid way to find Fragments in a ViewPager byTag but you can find them like below:
This method generates default Tag for a Fragment in ViewPager.
#NonNull
public static String generateViewPagerFragmentTag(int viewPagerId, int position) {
final StringBuilder tagBuilder = new StringBuilder();
tagBuilder.append("android:switcher:");
tagBuilder.append(viewPagerId);
tagBuilder.append(":");
tagBuilder.append(position);
return tagBuilder.toString();
}
Than you can use this tag to find your Fragment with findFragmentByTag method.
Also as mentioned in comments you can use an EventBus library to achieve what you want to do but be careful of Fragment Lifecycle states in ViewPager because the Fragment you want to communicate can be in onPause state an your changes canned be received. This depends on in which lifecycle method you subscribe and unsubscribe to bus. (If you are using OttoBus you can get no subscribers found for this event exception.) You can do same pattern with interfaces. You can create an interface and implement in your Fragments.
For an other solution, you can directly access our fragments in your ViewPager. Here's my answer for another question it.
Finally, as i mentioned at the beginning i think you should implement a solution which updates your data when user switched to specific tab of ViewPager. You can keep your data changes in a mem-cache and listen tab changes and update your view when user exactly navigated to that screen and ready to see data changes. You can check Repository pattern for a more complex solution.
I'm using this sample to create a simple app, it's made by Joshep Raco (JoeRock11)
https://github.com/JoeRock11/Xamarin_DesignLibrary
I have a problem, I was working with this sample to create a tabbed app, and I found an strange behavior, when the app is starting only the fragment1 and fragment2 loads , but not the fragment3 (when I say load I mean their oncreate and oncreateview method gets triggered ).
To load the fragment3 I have to explicity click it, I thought all the fragments would get load but is not the case, don't know why, I would like to know why this happens.
Also, and this is my main problem and I dont know how to fix it, every time I click a fragment tab, I want this fragment to reaload, now seems like it just shows the instantiated fragment on memory, because it doens't triger any method of the fragment, I need to load it again, because I need to refresh its data, now it only reloads randomly. can you or anyone help me solve this please?
Thanks a lot in advance.
pd: sorry for my english, it's not my mother language.
I believe this is controlled by the Off Screen Page Limit on the ViewPager control. This value defaults to 1, which basically means, the number of the pages the ViewPager should load at a time is 2, the page that is currently displayed, and the one off screen.
This value can be easily adjusted using the OffscreenPageLimit property on the ViewPager.
http://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit(int)
If you want all Fragments to reload data once they are slid on to the screen, you can override UserVisibleHint on each Fragmentand then check to see if the Fragment is visible and execute some code to reload data for that fragment:
public override bool UserVisibleHint {
get {
return base.UserVisibleHint;
}
set {
base.UserVisibleHint = value;
if (value) {
//Fragment is visible, reload data
}
}
}
http://developer.android.com/reference/android/support/v4/app/Fragment.html#setUserVisibleHint(boolean)
Another approach to updating the ViewPager fragments is outlined here: Update ViewPager dynamically?
Is there some way I get already created currently displayed same instance of fragment in my activity. I DON'T to use
findFragmentById(int id), simply I never created that
findFragmentByTag(String tag), because I am not adding tag in every fragment .offcourse due to some requirement.
getFragment(Bundle bundle, String key), because I never am putting in bundle.
Although I may look like fool to mention that, but I want something like this. Is activity keep some fragment instance somewhere.??
What can be the best approach I can take to achieve this requirement.
UPDATE
Okay, so let me tell you why I can't use above methods. If I am adding various fragment in one activity, where I always want to come back to one fragment when back is clicked. (As we have in navigation drawer, u know). And unless there are inner fragment. so for that I don't want to add in the back stack.
Now even if I have the tag associated with my fragments, I cant say for 8 fragment if- else-if-else for getting the tag. That I know is not correct. So first two ways goes out of my option. Now third one. I exactly don't know where to keep it. And even if I keep it where will I get the bundle and key every time I just want my fragment.
You can get from fragment Manager
List<Fragment> fragList=fManager.getFragments();
for(Fragment fr: fragList){
String fragClassName = fr.getClass().getName();
if(fragClassName.equals(Abc.class.getName())){
Log.i("Fragment:","Abc");
}else if (fragClassName.equals(Xyz.class.getName())) {
Log.i("Fragment:","Xyz");
}
}
i got viewpager with 4 tabs .. in each tab there is a fragment.
my first tab is a fragment with a form (for example Users)
after i click save, the data is inserted in the database.
my second tab is another fragment with form but it uses data from the first tab (i am getting it from the database) to populate a spinner.
now after i successfully inserted data from my first tab, in my second tab the spinner is empty. since my db query is implemented in onCreateView() in the second fragment, its called only once when the application is started, so changing between tab 1 i tab2 doesn't starts onCreateView() or even onResume().
The interesting thing for me is that if i go to tab4 and then return to tab2, my data is in my spinner properly, so somehow swiping two tabs away from my current tab refreshing my fragment.
my question is how can i achieve proper refresh to my fragment when onCreateView() is called only once ?
Edit
i tried to put that method psykhi suggested like that:
this.mViewPager.setOffscreenPageLimit(0);
this.mViewPager.setAdapter(this.mPagerAdapter);
this.mViewPager.setOnPageChangeListener(this);
but it's not working for me. Am i missing something ?
The best way that I have discovered to simulate a "setOffscreenPageLimit(0)" behavior is by using an OnPageChangeListener in conjunction with overriding the getItemPosition method in your adapter. Something like this:
In your custom pager adapter:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
Then in your Activity containing the ViewPager:
final MyPagerAdapter adapter = new MyPagerAdapter();
pager.setAdapter(adapter);
pager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
... anything you may need to do to handle pager state ...
adapter.notifyDataSetChanged(); //this line will force all pages to be loaded fresh when changing between fragments
}
}
This should have the same effect as setting the OffscreenPageLimit to 0. However, this functionality is sort of against what the ViewPager is designed to provide. If you are trying to implement a ViewPager in this way, it may be worth reevaluating your layout to be sure that a ViewPager is really what you want to use.
UPDATE: There is a better way, Please have a look here: Update ViewPager dynamically?
Removing content of this answer because it was a really bad way to access Fragments inside ViewPager, please see the above link for better approach.
It's because you can specify the number of fragment your viewpager will "keep in RAM" (setOffScreenPageLimit () :I think the default is 1). So your second fragment is not reloaded. And when you go to a tab 3 or 4, then your 2 firsts fragments are deleted, then recreated when you come back.
To refresh your fragment, there is many way to do it: you could implement a listener interface of your own in it to tell it when to refresh, or simply call a method that you would implement to update your contents.
first override in the fragment method
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
actionView();//call the method to be refreshed
}
else{
//no
}
}
In my experience, ViewPagers keep:
Your current tab
& 1 tab either side in memory
So when you switch between 1 and 2, nothing is happening under the hood - it's like they are simply being hidden / shown.
But when you navigate to tab 4, tabs 1 & 2 are destroyed (onDestroy() called), so when you navigate back to either of them, they are being re-created fresh (onCreate() called).
As psykhi suggests, you could setOffScreenPageLimit() to 0, so that each fragment is created every time you navigate to it.
If you were interested in keeping the other pages in memory for performance purposes, as they were designed with this is mind, you could use a messaging/event publishing system to send a message from tab 1 to tab 2 telling it to update when you submit the form on tab 1.
Ok may be a late reply,might help others in future, so recently I faced same issue while fetching data from db into tabs it used to display only once I click on 3rd tab.. I tried above mentioned solution but nothing really worked.. fianlly i came across Otto
This library allows you to communicate within your app..
Just use this library to yourAdapter.notifyDataSetChanged() wherever you fetching your data from db..
This video helped me alot https://www.youtube.com/watch?v=lVqBmGK3VuA