What I would like to achieve:
I have a flow where a user can choose among groups/categories of items (first tab) and see the list of items (in the second tab) and do other things in the third tab (not important here)
When the user lands on the application the first time the default group/category is chosen (in the first tab) and the corresponding list of items is displayed (in the second tab)
When the user selects a different group (in the first tab) I would like to change the list (in the second tab).
so the main question is:
What is the best and correct way to achieve this?
My current Implementation:
UI: Swipe Views with Tabs
I've choosen a layout with tabs and swipe view. Each tab is a different fragment declared in a FragmentPageAdapter. Everything contained in a ViewPager as explained in tutorial at https://developer.android.com/training/implementing-navigation/lateral.html
Backend: Firebase
Each fragment takes data from Firebase DB as list of objects through a FirebaseRecyclerAdapter.
Trying different solutions... What do you think is the best approach?
1. Use a different Layout?
Implement the user flow in a different way without swipe views
For example, It could be a floating button to first choose a group of items and then see them in another activity, but in this way coming back to groups/categories is not easy as with swipe views.
2. Dinamycally Change Fragment in TAB2?
When the user select a group in TAB1 create a new fragment initalised with a different DatabaseReference and change it in TAB2 through FragmentManager.
I've read many posts here on StackOverflow about dinamically changing
fragment in ViewPager but I didn't find really a definitive way to do it. (nested fragments? holder fragment for viewpager?...)
3. Change the Firebase backend reference only?
The idea is to change the Firebase DatabaseReference used to initialise the FirebaseRecyclerAdapter so that a new list of items is loaded, using the same fragment declared in FragmentPageAdapter.
I still didn't find a solution on how to do this
Before speaking of implementations... any idea about the best approach?
In my opinion, I will use EventBus or RxJava.Another implemention is :
STEP1:
Send chosen data in your first tab to Activity.
STEP2:
Send data to second tab from Activity
STEP3:
In second fragment, you can use load data from database in onCreateView
STEP4:
If your second fragment uses single pattern,you can call adapter.setList(yourList) and adapter.notifyDataChanged()
Easy Solution I found
I used the approach to change the Firebase Adapter using the same fragment already declared in ViewPager and it works fine.
I post here below my code if someone has the same scenario and want to implement in the same way.
In the FyrebaseRecyclerAdapter of the first Fragment (first tab), in populate view method you just create a new Firebase Database Reference and create a new adapter to set in the recyclerview.
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String groupKey = getRef(position).getKey();
// Getting the RecyclerView used by Second Fragment (Second TAB)
RecyclerView mRecyclerView = (RecyclerView)fragmentContainer.getActivity().findViewById(R.id.second_recycler_view);
FirebaseDatabase db = FirebaseDatabase.getInstance();
DatabaseReference dbRef = db.getReference("Groups").child(groupKey)
// Set the new adapter so that fragment 2 update data
FirebaseRecyclerAdapter<Member, MemberViewHolder> mAdapter = new MyAdapter(Member.class, R.layout.member, MemberViewHolder.class, dbRef);
mRecyclerView.setAdapter(mAdapter);
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
I know this is somewhat of a design question but I do have specific questions for it. I'm trying to understand how to handle a situation like this one:
Let's say I have a RecyclerViewFragment which loads a RecyclerView containing a bunch of Toy objects.
In one situation: Maybe this RecyclerViewFragment is part of a ViewPager on main display. There is a FloatingActionButton add-button present over this RecyclerView. You click the + button and you can add a new Toy to the list. Or you can click a Toy from the list directly and a floating menu pops up with Edit/Delete buttons, and pressing Edit lets you edit the Toy's details in a DialogFragment, or clicking Delete removes it from the RecyclerView.
In another situation: Now I am in a separate part of the app where I want to choose toys to use. So I press a button and a DialogFragment appears with a RecyclerView of Toys. I can click a Toy and it'll be added to my cart.
It seems like I should be re-using the same RecyclerView code in both situations, since they both involve a list of the same Toys. The only difference is that in one situation, I can add Toys and edit Toy details, and in the other situation, there is no Add button and clicking on a toy does something different (adding to a cart as opposed to bringing up an Edit/Delete dialog).
Is this the correct way to handle this:
Communication from Fragment to Activity: Interfaces? Have the RecyclerViewFragment, in the onAttach method, assign a listener of my design to the context. Then when a row of the RecyclerView is pressed, the callback is triggered. Now the underlying Activity can decide what to do with that press -- show the Edit/Delete dialog in one situation, add the Toy to a Cart in the other situation. Either way, the click item sends the Toy to the calling Activity so it can decide what to do with it.
Communication from Activity to Fragment: Now what about the situation with the Add button? This Add button would not be intrinsically part of the RecyclerViewFragment, so when I click Add, it would bring up the details dialog box where I can give the Toy details, and then press OK to add it. So somehow I have to transfer this new Toy to the Fragment to have it added to the RecyclerView. Would I simply do something like this:
RecyclerViewFragment recyclerViewFragment = (RecyclerViewFragment ) getSupportFragmentManager().findFragmentByTag("TOY_RECYCLERVIEW");
recyclerViewFragment.getNewToyAndRefreshList(newToy);
and then in the RecyclerViewFragment:
public void getNewToyAndRefreshList(Toy newToy) {
toyList.add(newToy);
Collections.sort(toyList); //Toy has Comparable implemented, sort by name
recyclerViewAdapter.notifyDataSetChanged();
}
Am I on the right track? Is there a different way to fix this situation?
That's certainly a design question, but IMHO there's a very specific issue on it and I believe it's a good question (reason I'm answering), but that also means other developers might have other approaches to solve the issue.
1. that is a totally fair and acceptable approach to it. You let the fragment be simple UI element and let someone else (the activity) implement the click behavior.
For this approach remember to code it only against the interface. That means, don't cast it to your activity. For example:
// do this
toyClickListener.onToyClicked(toy);
// don't do this
((MyActivity)getActivity()).onToyClicked(toy);
That way you keep the "simple UI element" be completely unaware of who is implementing the behavior.
2. IMO for this kind of scenario (specially on RecyclerView.Adapter) the best thing to do is to forget the UI and only focus on the data. And how speciafically you implement this, will vary on what is your data source.
But the base idea is that you have somewhere a data repo (DB?) and anyone using data from there, should subscribe to changes to it.
So you override RecyclerView.Adapter.registerAdapterDataObserver and unregisterAdapterDataObserver add the subscription/listener code, something like that:
#Override registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
super.registerAdapterDataObserver(observer);
db.subscribe(this, toyList);
}
#Override unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
db.unsubscribe(this);
super.unregisterAdapterDataObserver(observer);
}
#Override public void onDbDataUpdate(new Data comes here){
update the data, and call .notifyDataSetChanged();
}
that way once the FAB + and then dialog is clicked the new Toy gets added to the DB and the adapter gets "automatically" notified.
So if this data comes from a SQLite you can call on the cursor registerContentObserver if it's a RealmDB you'll use addChangeListener, even Android databinding libraries have a ObservableList
I made an activity which is simply a viewpager with a few tabs. Each tab is like a form that the user needs to fill out. When the user is done, the data from the view pager should be collected and sent back as a result. I almost have this working but for some reason the data on my first tab seems to get reset when the user gets to the third tab. I'm guessing this is due to some view recycling in the pager. Anyways, I'm wondering if there is some easy way to gather all the data from the different tabs or do I have to create some kind of tight coupling between the activity and viewpager object?
you have to use SmartFragmentStatePagerAdapter as described here "https://guides.codepath.com/android/ViewPager-with-FragmentPagerAdapter" and than need to
vpPager.setOffscreenPageLimit(3);
Your issue should be resolved. :)
A viewpager will kill off the fragment if it is 2 pages away from the current page. It then recreates it when the pager is on a page that is 1 step away from it.
You should move your form data into your activity/fragment that is holding the viewpager and use fragment listeners to update your data accordingly
For example, you could use a fragment listener like below to pass the data to/from the containing activity
public interface MyFragmentListener{
void saveMyFormData(MyFormData formData);
MyFormData getFormData();
}
private MyFragmentListener mListener;
//initialise fragment listener in onAttach (or elsewhere)
private void initFormView(){
MyFormData data = mListener.getFormData();
//do stuff with data
}
private void saveData(){
mListener.saveMyFormData(myFormDataObject);
}
i have 2 fragments
in 1st fragment i am calling a 2nd fragment on button click which returns data(on click on list items) and again open 1st fragment with the result displayed (i.e some String data of list item).
But the problem is when i comeback from 2nd to 1st again after selection in 2nd fragment my 1st fragment is displayed only but do not react on button click.
this is my first fragment
i have searched lot but i found activity fragment communication only not fragment to fragment
if any body has solution for fragment to fragment only.please help
There are a lot of ways, here some hints.
Generally speaking you can use "listeners" (interfaces)
Official Google Docs
Similar question on StackOverflow
More elegant way using "event bus" paradigm, a clean and simple library is Otto
Another (slightly dirty) way if you are using the compatibility library would be to use the LocalBroadcastManager. Your fragments can send (and listen for) local broadcasts to notify each other of events...
Try like this (Example an integer array):
//Create a public class:
public class Values{
public static int[] val = null;
}
//Set array in one fragment:
Values.val = int[] arr1;//arr1 containing your values
//Get array in another fragment:
int[] arr2 = Values.val;
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