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.
Related
I have a dilemma how to implement a particular functionality. I need to display a list of pages, where each page has its own list of sections.
The way how it is now implemented, i have an Activity and a Fragment that have reference to the same ViewModel. From the NavigationDrawer when i select a concrete page, i change the list of sections that is displayed in the Fragment. The LiveData attributes are shown in the snippet.
public LiveData<List<Page>> pages;
public MutableLiveData<Page> selectedPage = new MutableLiveData<>(new Page(1));
public LiveData<List<Section>> sections = Transformations.switchMap(this.selectedPage, (Page page) ->
this.pageRepository.getSectionsForPage(page.id)
);
Now, besides the NavigationDrawer, i wanted to add swipe possibility of changing pages, and that led me to using ViewPager. But because that leads to using multiple Fragments of the same type, i'm wondering if using a shared ViewModel for every instance is a good option? In that way, every fragment shows totally the same data. The other way would be to make a new specific ViewModel for every instance, which i'm not sure if it is a good idea, because i can probably have as many as 50 pages.
A lot of pages possibility is maybe also not a use case for a ViewPager, but i'm not sure about that, because my Android experience is low.
Any tip with an explanation why is really appreciated.
If you are using sharedViewModel then it will be attached to activity ........ if you are using separate viewModel then it is attached with the lifeCycle of Fragment
When viewpager displays one fragment, it will automatically load the fragment pages around it for performance reasons.
In my fragments, i have recycleviews with a popup menu to delete one item in the list.
I am facing a problem of deleting one item from one fragment, but that item still exists in the other preloaded fragments after I scroll to them.
It works only if I force the viewpager to reload the contents of its fragments by manually scrolling back and forth the fragments.
Is there a way to force reload the preloaded fragments by viewpager?
Your problem can be solved by using Interface. Google suggest using callbacks\listeners that are managed by your main Activity for communicating between fragments.You can use Interface which tells the other fragment to refresh its listview when you delete an item in current fragment.
For an overview http://developer.android.com/training/basics/fragments/communicating.html
Also a good question about this How to pass data between fragments
First create an interface to detect changes in your RecyclerView:
public interface MyRecyclerViewChangeListener(){
void onRecyclerViewDataChanged(int id);
}
Create a static variable in your Fragment or Activity which contains your viewpager:
public static List<MyRecyclerViewChangeListener> mListeners = new ArrayList();
Implement your interface to your ViewPagerFragments and do what you want in method you implemented.
In your fragment's onResume register your listener to mListeners like blow to detect changes:
MyFragmentOrActivity.mListeners.add(this);
And in your fragment's onPause unregister your listener:
MyFragmentOrActivity.mListeners.remove(this);
Finally notify your listeners when your recyclerview data changed:
for(MyRecyclerViewChangeListener listener : mListeners){
listener.onRecyclerViewDataChanged(id);
}
Edit : If you are changing your recyclerview's data after an async task result such as a web api call, you can register your listener in fragment's onCreateView method and un register in onDestroyView method. So you can catch changes in your fragments.
I am not sure if i'm getting your question right but i think this should do it.
YourViewpager.setOffscreenPageLimit(0);
Now the fragment should be destroyed if it is not active and will be recreated if you open it again.So the data change should be recognized.
Hope I could help
Try setting mViewPager.setOffscreenPageLimit(1) such that it will not pre-cache any fragment or you could use FragmentStatePagerAdapter inside viewPager to achieve what you want.
Edit 1:
So Conclusion is :
1) Use local broadcast mechanism to update the fragments present in ViewPager adapter
2) Use handler mechanism to refresh these fragments
3) if you want to blindly update these fragments once they are visible to users then do it inside onPageChangeListener of view pager method.
The answers helped me find the solution.
For future reference, I used a callback every time I used deleteItem(), and took a list of the loaded frags by using the method [FragmentHostingViewPager].getChildFragmentManager().getFragments()
Then I iterated through each fragment as long as each fragment was not null, and called a refresh() method on them.
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.
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 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.