Custom adapter not showing data in tab - android

I have an activity with tabs, in each tab I load the same fragment to show different lists of data.
Although it's the same fragment and therefor the adapter is the same, and the information is loaded in the same way one of the fragment's RecyclerView doesn't show the information.
I know the data is getting there because I debugged it and also because otherwise it would be showing a text in the screen saying there is no data to show.
I have 4 or 5 tabs, the first two always load just fine. But on the others it seems like the onCreateView isn't completely executed.
This is called from the onCreateView.
if (offers.size() != 0)
noOffers.setVisibility(View.GONE); //this gets execute, it hide some text that otherwise would be on the left image
else
content.setVisibility(View.GONE);
this.offers = offers;
MyOffersAdapter adapter = new MyOffersAdapter(getActivity(), this.offers, user);
rvOffers.setAdapter(adapter);
rvOffers.setLayoutManager(new LinearLayoutManager(getActivity())); //this part is getting executed in all the tabs
Although it reaches the last line neither onCreateViewHolder nor onBindViewHolderof the adapter get execute for the tabs beyond the second one.
This is the code I'm using to create the after setting the tabs:
viewPager.setAdapter(adapter);
tabs.setupWithViewPager(viewPager);
viewPager.setOffscreenPageLimit(5);
tabs.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
}
});
Is there a way I can force those methods to be executed when the tab is clicked? Or what am I configuring wrong?

Related

What's the correct way of displaying ViewPager after associated ListView's item click?

I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).

Changing reenter animation to another item of a list

I have a RecyclerView with images and when I press an image the app opens another activity that contains a ViewPager with the same images but in the position of the one I selected.
I've done the transition in Lollipop to share this image between activities using supportPostponeEnterTransition and supportStartPostponedEnterTransition in the called activity to wait until the viewPager is loaded with images to start the transition.
When I enter in the called activity and when I press back the transitions are ok.
The problem I'm facing is if I move to another image in the ViewPager of the called activity, when I press back it animates the image that it was selected at the beginning, not the currently selected one.
I've been able to change the animated image to the one selected in the called activity with this:
#Override
public void onBackPressed() {
View view = ((ImageDetailFragment) adapter.getFragment(viewPager,
viewPager.getCurrentItem())).getTransitionView();
ViewCompat.setTransitionName(view, Constants.TRANSITION_IMAGE);
super.onBackPressed();
}
But it is returning to the same position of the original image in the list of the calling activity.
How can I do it to make the image return to its position in the list of the calling activity?
The first thing to do is to make sure that the views work properly without any Activity transition. That is, when your return from Activity with the ViewPager, the RecyclerView Activity should be showing the View that the ViewPage was showing. When you call the ViewPager activity, use startActivityForResult and use the result to scroll the RecyclerView to the correct position.
Once that is working, the Activity Transition can be made to work. I know that you've given each View in your RecyclerView a different transitionName, right? When you bind the View, call setTransitionName and give it a repeatable name. Typically this is the image URL or cursor row ID or at worst some munged index like "image_" + index.
The next thing you need to do is set the SharedElementCallback for both the calling Activity (exit) and called activity (enter). In each, you're going to need to override the onMapSharedElements callback to remap the shared element.
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// assuming just one shared element, excuse the ugly code
sharedElements.put(names.get(0), mSharedElement);
}
Here, mSharedElement has been set in the onActivityReenter in the calling (RecyclerView) activity and in onCreate and prior to finishAfterTransition (onBackPressed?) in the called (ViewPager) activity.
The onActivityReenter gives new functionality specifically for this case. You can look at the results there before the called Activity completes.

How to Load only current pages of Fragment

I have involved with Fragments page-loads problem. I create a 10-Fragments and identify the current Fragment by onPageChanged(). I am updating the contents of each Fragment during swipe.
but if i am doing to update one Fragment and show values. it is displaying wrong index value at wrong pages.
I noted, Fragment is loading previous and next pages also. so if i display current fragment values by changing the resources, it changes previous and next resources but not current.
example:- if i am in pageno-3, if display the update values of pageno-3 at pageno-4 , it happens during left to right swipe
but i will do right to left swipe, it display the update values of pageno-3 at pageno-2
I think you should extend the Fragment class. Inside the new class define a method
public void refresh(NewValuesObject obj){
... do something with the new values
};
Inside your onPageChanged callback call this method with the desired params.
You may want to save your fragment instances and do the updates (call refresh) for the fragment at the previous or next index.
Hope it helps.
You can set control in visiblity like this:
#Override
public void setMenuVisibility(final boolean visible) {
super.setMenuVisibility(visible);
if((visible ) )
{
//set your adapter or listview
}
}
if you set this in your 2 and 4 , when you come 3. page the other ones does not triggered.

My fragments in viewpager tab dont refresh

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

How to recreate the listview of a former ListFragment after pressing back button

I have a ListFragment with a listview in it, that contains a navigation-structure. By selecting an item of the list, the next navigation hierarchy stage should be displayed. And so on.
The next hierarchy stage is created by a new fragment (remove the old fragment and add the same fragment new via fragmant transactions by using addToBackStack), in which the arrayadapter of the new listview is updated to the items of the next navigation hierarchy. That work pretty fine.
I want the user to have the possibility to navigate back via the back button. And here start my problems. I have no clue how to save the listview so it could be recreated after using the back button.
I thought using onSaveInstanceState would be a good idea. But onSaveInstanceState ist not called when the transaction gets comitted. I checked it by placing a Log.d("..","...) in the method. It´s not called. (What I found out, is, that onSaveInstanceState is called when rotating from portrait- to landscape-view and vice-versa. But that´s no help for my problem).
So what would be the best idea to store the elements of the listview and get it back to recreate the listview after using the back button and the former fragment is getting created? All items are Strings that are stored in an ArrayList that is bound to the ListAdapter.
Here my code. I implemented an interface, via the click on an item in the listview calls a method in the parent-activity. In this method I first call a method to fill the already existing ArrayList (navigationsItems) with the new items that are content of the next navigation-stage:
The code of my ArrayAdapter:
arrayAdapterFuerNaviList = new ArrayAdapter<String>(
BRC_BoardOverviewActivity.this,
android.R.layout.simple_list_item_1,
navigationsItems);
In this method I first call a method to fill an ArrayList (navigationsItems) with the items of the next navigation-stage:
// load new itemlist to navigation-
public void inhaltAktuelleNaviListeInArrayLaden() {
navigationsItems.clear();
for (int i = 0; i < prefs.getInt("aktuelleNaviListe_size", 0); i++) {
(...)
navigationsItems.add(titel);
}
}
Then I call the method, with which I load the new ListFragment and bind the new ArrayAdapter to it:
// push navifragment to BackStack and create a new instance of it
public void naviFragmenteNeuLaden() {
// get a reference to the actual ListFragment
Fragment removefragment = frgm.findFragmentById(R.id.navi_container);
// initialize ne ListFragment
BRC_NavigationsFragment navifragment = (BRC_NavigationsFragment) frgm
.findFragmentById(R.id.navi_container);
// remove the old and bind the new ListFragment from/to the container ...
FragmentTransaction frgmta = frgm.beginTransaction();
frgmta.remove(removefragment);
frgmta.add(R.id.navi_container, navifragment);
// ... push the old ListFragment on the BackStack and commit ...
frgmta.addToBackStack(null);
frgmta.commit();
frgm.executePendingTransactions();
// ... bind the updated ArrayAdapter to the new ListFragment ...
try {
navifragment.setListAdapter(arrayAdapterFuerNaviList);
} catch (Exception e) {
// TODO
e.printStackTrace();
}
// ... and notify
try {
arrayAdapterFuerNaviList.notifyDataSetChanged();
} catch (Exception e) {
// TODO
e.printStackTrace();
}
}
As mentioned, forward-navigating is no problem. But when i press back button for e.g. in the 2nd navigation-stage, the former ListFragment gets loaded (verfified with Log.d("..."."...)-Messages in onCreatView() of ListFragment), but the former listview which was in it isn´t created.
I am currently working on a new approach. When you click on an item in the list, I write the current list, together with a reference to the current ListFragment into a vector based stack. Then I catch with "onBackPressed()"-method the press of the back button. So now when the button is pressed, I call on the already mentioned methods (see above), write the item-data from the stack back into the list, call via the stored reference the forme ListFragmt and bind the updated ArrayAdapter to it. This is work in progress actually. I will write the result of this approach when finished.
I solved the problem.
For the different navigation contents of the ListView I have defined a class "Naviliste", in which all the items are stored. If someone clicks on an item, then the a new listview with the correspondingly new list will be generated. These lists are indexed, so each list get´s a fixed ID assigned.
By clicking an item in the ListView, the ID of the current Naviliste is pushed onto a stack. After that a new fragment will be created an the new list bound to it. The same procedure repeats if the user goes a step further, and so on ...
If the user pushes the back button to go a step back, the following is done:
The push of the back button is catched via the onPressedBack()-method placed in the parent activity. Via this method an own method is called. This pulls the last ID that was pushed onto the stack and then builds with it in a new created fragment the former list.
At the same time I will run along a counter that counts in which navigation depth I am. In each navigation step forward it get´s inceased by 1. And in reverse just reduced by 1. With this counter I am able to query if the user is in the navigation-root (counter = 0) and once more pushes the back button to leave the current activity. If so, i call finish() and the parent activity with its fragments get´s closed. Voilá!
It works great. Took a little bit time to get over it. But now I am happy that my solution works. :)

Categories

Resources