RecyclerView onBindViewHolder called only once inside Tab layout - android

I've four tabs and four fragments (each one for each tab).
Each fragment has a vertical recycler view. Since all fragments view look similar I'm re-using the same layout file, same recycler view items and same adapter.
The issue is that only one item is loaded under the first tab and third tab and fourth tab, While the second tab successfully loads the entire data.
I hope image added below gives better understanding regarding the issue.
Here is my adapter code
public class OthersAdapter extends RecyclerView.Adapter<OthersAdapter.OthersViewHolder> {
private final Context context;
private final ArrayList<LocalDealsDataFields> othersDataArray;
private LayoutInflater layoutInflater;
public OthersAdapter(Context context, ArrayList<LocalDealsDataFields> othersDataArray) {
this.context = context;
this.othersDataArray = othersDataArray;
if (this.context != null) {
layoutInflater = LayoutInflater.from(this.context);
}
}
class OthersViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView othersSmallTitleTextView;
ImageView othersImageView;
OthersViewHolder(View itemView) {
super(itemView);
othersSmallTitleTextView = (TextView) itemView.findViewById(R.id.others_small_title);
othersImageView = (ImageView) itemView.findViewById(R.id.others_image);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
Intent couponDetailsItem = new Intent(context, LocalDealsActivity.class);
Bundle extras = new Bundle();
extras.putString(Constants.SECTION_NAME, context.getString(R.string.local_deals_section_title));
// Add the offer id to the extras. This will be used to retrieve the coupon details
// in the next activity
extras.putInt(Constants.COUPONS_OFFER_ID, othersDataArray.get(
getAdapterPosition()).getLocalDealId());
couponDetailsItem.putExtras(extras);
context.startActivity(couponDetailsItem);
}
}
#Override
public OthersViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = layoutInflater.inflate(R.layout.others_items, parent, false);
return new OthersViewHolder(view);
}
#Override
public void onBindViewHolder(OthersViewHolder holder, int position) {
String lfImage = othersDataArray.get(position).getLocalDealImage();
String lfCategoryName = othersDataArray.get(position).getLocalDealSecondTitle();
if (lfCategoryName != null) {
// Set the second title
holder.othersSmallTitleTextView.setText(lfCategoryName);
}
if (lfImage != null) {
if (!lfImage.isEmpty()) {
// Get the Uri
Uri lfUriImage = Uri.parse(lfImage);
// Load the Image
Picasso.with(context).load(lfUriImage).into(holder.othersImageView);
}
}
}
#Override
public int getItemCount() {
return othersDataArray.size();
}
}
I like to point out couple of things -
I've checked other answers on Stack Overflow. They talk about setting the recycler view layout_height to wrap_content. This isn't the issue as the layout_height is already wrap_content and also the second tab loads all the data as expected.
And some others answers mentioned to used same versions for all support libraries and I'm already using 25.1.0 version for all the support libraries.
Size of the data array is 20 and returning 20 from the adapter's getItemCount() method.
The data array has the expected number of items in it and they are not null or empty.
Clean build, invalidate/caches doesn't work either.
Finally, I'm using FragmentStatePagerAdapter to load the fragments when the tabs are in focus.
EDIT:
This is how I'm parsing the JSON data received
private void parseLocalDeals(String stringResponse) throws JSONException {
JSONArray localJSONArray = new JSONArray(stringResponse);
// If the array length is less than 10 then display to the end of the JSON data or else
// display 10 items.
int localArrayLength = localJSONArray.length() <= 20 ? localJSONArray.length() : 20;
for (int i = 0; i < localArrayLength; i++) {
// Initialize Temporary variables
int localProductId = 0;
String localSecondTitle = null;
String localImageUrlString = null;
JSONObject localJSONObject = localJSONArray.getJSONObject(i);
if (localJSONObject.has(JSONKeys.KEY_LOCAL_DEAL_ID)) {
localProductId = localJSONObject.getInt(JSONKeys.KEY_LOCAL_DEAL_ID);
}
if (localJSONObject.has(JSONKeys.KEY_LOCAL_DEAL_CATEGORY)) {
localSecondTitle = localJSONObject.getString(JSONKeys.KEY_LOCAL_DEAL_CATEGORY);
}
if (localJSONObject.has(JSONKeys.KEY_LOCAL_DEAL_IMAGE)) {
localImageUrlString = localJSONObject.getString(JSONKeys.KEY_LOCAL_DEAL_IMAGE);
}
if (localImageUrlString != null) {
if (!localImageUrlString.isEmpty()) {
// Remove the dots at the start of the Product Image String
while (localImageUrlString.charAt(0) == '.') {
localImageUrlString = localImageUrlString.replaceFirst(".", "");
}
// Replace the spaces in the url with %20 (useful if there is any)
localImageUrlString = localImageUrlString.replaceAll(" ", "%20");
}
}
LocalDealsDataFields localDealsData = new LocalDealsDataFields();
localDealsData.setLocalDealId(localProductId);
localDealsData.setLocalDealSecondTitle(localSecondTitle);
localDealsData.setLocalDealImage(localImageUrlString);
localDealsDataArray.add(localDealsData);
}
// Initialize the Local Deals List only once and notify the adapter that data set has changed
// from second time. If you initializeRV the localDealsRVAdapter at an early instance and only
// use the notifyDataSetChanged method here then the adapter doesn't update the data. This is
// because the adapter won't update items if the number of previously populated items is zero.
if (localDealsCount == 0) {
if (localArrayLength != 0) {
// Populate the Local Deals list
// Specify an adapter
localDealsRVAdapter = new OthersAdapter(context, localDealsDataArray);
localDealsRecyclerView.setAdapter(localDealsRVAdapter);
} else {
// localArrayLength is 0; which means there are no rv elements to show.
// So, remove the layout
contentMain.setVisibility(View.GONE);
// Show no results layout
showNoResultsIfNoData(localArrayLength);
}
} else {
// Notify the adapter that data set has changed
localDealsRVAdapter.notifyDataSetChanged();
}
// Increase the count since parsing the first set of results are returned
localDealsCount = localDealsCount + 20;
// Remove the progress bar and show the content
prcVisibility.success();
}
parseLocalDeals method is inside a helper class and it is called by using initializeHotels.initializeRV();
initializeRV() initializes the Recycler view, makes a network call to the server and the received data is passed to the parseLocalDeals method. initializeHotels being an instance variable of the Helper class.
EDIT 2:
For those who wants to explore the code in detail, I've moved the part of the code to another project and shared it on Github. Here is the link https://github.com/gSrikar/TabLayout and to understand the hierarchy check out the README file.
Can anyone tell me what I'm missing?

Not much of an answer but too long for a comment.
I have duplicated (almost) your adapter code and it fully works for me. I believe I have done the same as you. I'm using the same layout file, the same item & same adapter for all tabs. I think there are no problems with your adapter code.
I say 'almost' because I had to change a couple of things since I don't have access to your data. I changed your LocalDealsDataField model to include a BitmapDrawable & I changed onBindViewHolder() to handle it.
BitmapDrawable lfImage = othersDataArray.get(position).getLocalDealImage();
holder.othersImageView.setBackground(lfImage);
Since there seems to be no problem with your adapter, I would focus on getting the data or setting up the adapter as your problem. Sorry I can't be of help beyond that.
FYI, here's how I setup the adapter in onCreateView()
rootView = inflater.inflate(R.layout.recycler_view, container, false);
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mAdapter = new OthersAdapter(this.getContext(), list);
mRecyclerView.setAdapter(mAdapter);

Summary
Solved the layout issue at point 1 replacing a LinearLayout by a RelativeLayout, inverting visibility logic to avoid ghost effect and catching exceptions and preventing them when the related view is not found.
Added point 2 to demonstrate that the visual defect is only present on Marshmallow and Nougat devices.
Finally FragmentStatePagerAdapter loads pages before getting focus so a fix is proposed at point 3 (load all pages and update them when are selected).
Further information in the comments below and #d4h answer.
The fourth page is not using the same layout, only the same RecyclerView and id, perhaps a work in progress. The layout issue can be solved using the same layout that previous pages but I consider this change out of scope.
1. Partially fixed for Marshmallow and Nougat devices. Work in progress.
Update2 Changing LinearLayout by RelativeLayout and inverting visibility logic solves layout issue:
Update: Commenting initializeTrending in all the fragment initializations also works onApi23+
I'll check it later, seems as deals are correctly loaded but then trending is loaded and deals are lost. WIP here.
If trending array empty and trending view gone, deals are not shown, but using invisible are shown
2. You are loading a wrong page on Marshmallow and Nougat devices
FragmentStatePagerAdapter first call to getItem() wrong on Nougat devices
This ended up having nothing to do with the FragmentStatePagerAdapter
code. Rather, in my fragment, I grabbed a stored object from an array
using the string ("id") that I passed to the fragment in init. If I
grabbed that stored object by passing in the position of the object in
the array, there was no problem. Only occurs in devices with Android 7.
FragmentStatePagerAdapter - getItem
A FragmentStatePager adapter will load the current page, and one page
either side. That is why it logs 0 and 1 at the same time. When you
switch to page 2, it will load page 3 and keep page 1 in memory. Then
when you get to page 4 it will not load anything, as 4 was loaded when
you scrolled to 3 and there is nothing beyond that. So the int that
you're being given in getItem() is NOT the page that is currently
being viewed, is the one being loaded into memory. Hope that clears
things up for you
These comments are confirmed in this branch and commit
All pages load correctly on Lollipop emulator, the last page has an extra issue, see OthersFragment:
3. Initialize all pages at creation and update them on selection.
Increase OffScreenPageLimit so all pages are initialised
Add on page selected/unselected/reselected listener
These changes solve the issue commented below:
/**
* Implement the tab layout and view pager
*/
private void useSlidingTabViewPager() {
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
BottomSectionsPagerAdapter mBottomSectionsPagerAdapter = new BottomSectionsPagerAdapter(getChildFragmentManager());
// Set up the ViewPager with the sections adapter.
ViewPager mBottomViewPager = (ViewPager) rootView.findViewById(R.id.local_bottom_pager);
mBottomViewPager.setOffscreenPageLimit(mBottomSectionsPagerAdapter.getCount());
mBottomViewPager.setAdapter(mBottomSectionsPagerAdapter);
TabLayout tabLayout = (TabLayout) rootView.findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(mBottomViewPager);
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
/**
* Called when a tab enters the selected state.
*
* #param tab The tab that was selected
*/
#Override
public void onTabSelected(TabLayout.Tab tab) {
// TODO: update the selected page here
Log.i(LOG_TAG, "page " + tab.getPosition() + " selected.");
}
/**
* Called when a tab exits the selected state.
*
* #param tab The tab that was unselected
*/
#Override
public void onTabUnselected(TabLayout.Tab tab) {
// Do nothing
Log.i(LOG_TAG, "Page " + tab.getPosition() + " unselected and ");
}
/**
* Called when a tab that is already selected is chosen again by the user. Some applications
* may use this action to return to the top level of a category.
*
* #param tab The tab that was reselected.
*/
#Override
public void onTabReselected(TabLayout.Tab tab) {
// Do nothing
Log.i(LOG_TAG, "Page " + tab.getPosition() + " reselected.");
}
});
}
Previous Comments:
Check your LocalFragment getItem() method using breakpoints.
If you select one page, next page is also initialized, and you are sharing the recyclerView, etc.
I would move the initialization outside of getItem() as suggested here:
ViewPager is default to load the next page(Fragment) which you can't
change by setOffscreenPageLimit(0). But you can do something to hack.
You can implement onPageSelected function in Activity containing the
ViewPager. In the next Fragment(which you don't want to load), you
write a function let's say showViewContent() where you put in all
resource consuming init code and do nothing before onResume() method.
Then call showViewContent() function inside onPageSelected. Hope this
will help
Read these related questions (the first has possible workarounds to hack the limit to zero):
ViewPager.setOffscreenPageLimit(0) doesn't work as expected
Does ViewPager require a minimum of 1 offscreen pages?
Yes. If I am
reading the source code correctly, you should be getting a warning
about this in LogCat, something like:
Requested offscreen page limit 0 too small; defaulting to 1
viewPager.setOffscreenPageLimit(couponsPagerAdapter.getCount());
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}

I have looked at your code, problem is same as explained by #ardock
Solution i would like to propose,
You have to change your code at 3 place ::
Inside all Fragment You are using in ViewPager Don't call initializeRESPECTIVEView() from onCreateView method.
Inside LocalFragment make a list of Fragments you are going to use with ViewPager and pass it to BottomSectionsPagerAdapter. and return Fragment from that list from getItem(int position) of BottomSectionsPagerAdapter.
Add Following code to LocalFragment inside useSlidingTabViewPager().
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
//Call Respective fragment initializeRESPECTIVEView() method from onTabSelected , you can get fragment instance from list you passed to BottomSectionsPagerAdapter

Related

Viewpager optimization with custom views

Scenario - I am working on an app that shows events respective to particular dates in a viewpager, where each page represents a day(i.e 24 hrs in a vertical manner, similar to google calendar). Each page/fragment contains a vertical scrollview(it has a framelayout inside it) and based on list of events i am dynamically creating custom-views(view position and dimension is based on the corresponding event timing and duration) and adding it to the scrollview. User can drag and drop events between dates.
Issue - I have successfully achieved the view creation part, now the issue is with performance. Sometimes vewpager(using FragmentStatePagerAdapter) lags while swiping through pages.
Someone please suggest me how to reduce the lag or any better ways to achieve this
Yes I had a similar performance with viewpager and FragmentStatePagerAdapter, the problem with viewpager is that pre creates the views either side of the current view to speed up the swipe to next view.
This works well for static views but for views with dynamic data the pre-created view was usually out of date and needed to be regenerated when the user swiped to it.
Thus it was having to call onCreateView on 3 views while the user swiped between views leading to lag sometimes.
I thought of 2 improvements for performance, though only used one.
1) The view holder/model pattern e.g. https://www.androidcode.ninja/android-viewholder-pattern-example/ and https://developer.android.com/topic/libraries/architecture/viewmodel where the inflation and finding items re-used / separated from onCreateView activities.
I did not use this method
2) Create bare bones views for the dynamic ones (The first view was static and then there were 2 dynamic ones at positions 1 and 2 that contained listviews of different aspects of the dynamic data.)
The listviews were inflated and had an adapter set to an empty list in onCreateView of these views thus they were fast to create.
Then I added an on OnPageChangeListener which notified the adapter backing the ViewPager that the pages had changed
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
if (position == 1 || position == 2)
{
mSectionsPagerAdapter.notifyDataSetChanged();
}
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Code goes here
}
#Override
public void onPageScrollStateChanged(int state) {
// Code goes here
}
});
The notifyDataSetChanged causes the viewPager to call getItemPosition in the FragmentStatePagerAdapter class to work out if the page position has changed and it if need to re-create it in the a new position.
Then in FragmentStatePagerAdapter extended class I overrode getItemPosition with
#Override
public int getItemPosition(Object object) {
if (object instanceof LogFragment) {
LogFragment f = (LogFragment) object;
if (f != null) {
f.update();
}
} else if (object instanceof SummaryFragment){
SummaryFragment f = (SummaryFragment) object;
if (f != null) {
f.update();
}
} else {
return POSITION_UNCHANGED;
}
return super.getItemPosition(object);
}
This allowed me to get the Fragment object and then call the update method on it but still returning POSITION_UNCHANGED so the viewpager did not try and re-create the Fragments.
Then in the update method of the Fragment I get the listview adapter and update the data.
public void update(){
adapter.clear();
adapter.addAll(datasource.getData());
}
Thus the more costly getting and display the dynamic data is only done when the page is actually display, NOT when it is pre-created by viewPager (because that pre-created view would be old out of date data anyway and would need to be updated)
There is one downside to this approach, the screen shown during the swipe is still the old data (either empty or data from when that page was last updated), this was acceptable to me.

RecyclerView notifyItemRangeInserted not maintaining scroll position

I have a simple recyclerview with items (tips) and a loading spinner at the bottom.
here's how the item count and item view type methods look:
#Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) { // last position
return LOADING_FOOTER_VIEW_TYPE;
}
else {
return TIP_VIEW_TYPE;
}
}
#Override
public int getItemCount() {
return tips.size() + 1; // + 1 for the loading footer
}
basically, i just have a loading spinner under all my items.
I create the adapter once like so:
public TipsListAdapter(TipsActivity tipsActivity, ArrayList<Tip> tips) {
this.tipsActivity = tipsActivity;
this.tips = tips;
}
and then once i have fetched additional items, i call add like so:
public void addTips(List<Tip> tips) {
// hide the loading footer temporarily
isAdding = true;
notifyItemChanged(getItemCount() - 1);
// insert the new items
int insertPos = this.tips.size(); // this will basically give us the position of the loading spinner
this.tips.addAll(tips);
notifyItemRangeInserted(insertPos, tips.size());
// allow the loading footer to be shown again
isAdding = false;
notifyItemChanged(getItemCount() - 1);
}
What's odd here is that when i do that, the scroll position goes to the very bottom. It almost seems like it followed the loading spinner. This only happens on the first add (i.e. when there is only the loading spinner showing initally). subsequent adds maintains the proper scroll position (the position where the items were inserted).
This doesn't happen if i change notifyItemRangeInserted() to notifyItemRangeChanged() like so:
public void addTips(List<Tip> tips) {
// hide the loading footer temporarily
isAdding = true;
notifyItemChanged(getItemCount() - 1);
// insert the new items
int insertPos = this.tips.size(); // this will basically give us the position of the loading spinner
this.tips.addAll(tips);
notifyItemRangeChanged(insertPos, tips.size());
// allow the loading footer to be shown again
isAdding = false;
notifyItemChanged(getItemCount() - 1);
}
Nor does it happen if i simply call notifyDataSetChanged() like so:
public void addTips(List<Tip> tips) {
this.tips.addAll(tips);
notifyDataSetChanged();
}
Here's the code for setting the adapter in my Activity:
public void setAdapter(#NonNull ArrayList<Tip> tips) {
if (!tips.isEmpty()) { // won't be empty if restoring state
hideProgressBar();
}
tipsList.setAdapter(new TipsListAdapter(this, tips));
}
public void addTips(List<Tip> tips) {
hideProgressBar();
getAdapter().addTips(tips);
restorePageIfNecessary();
}
private TipsListAdapter getAdapter() {
return (TipsListAdapter) tipsList.getAdapter();
}
Note:
I don't manually set scroll position anywhere.
I call setAdapter() in onResume()
addTips() is called after I fetch items from the server
Let me know if you need any additional parts of my code.
This only happens on the first add (i.e. when there is only the loading spinner showing initally). subsequent adds maintains the proper scroll position (the position where the items were inserted).
RecyclerView has built-in behavior when calling the more-specific dataset change methods (like notifyItemRangeInserted() as opposed to notifyDataSetChanged()) that tries to keep the user looking at "the same thing" as before the operation.
When the data set changes, the first item the user can see is prioritized as the "anchor" to keep the user looking at approximately the same thing. If possible, the RecyclerView will try to keep this "anchor" view visible after the adapter update.
On the very first load, the first item (the only item) is the loading indicator. Therefore, when you load the new tips and update the adapter, this behavior will prioritize keeping the loading indicator on-screen. Since the loading indicator is kept at the end of the list, this will scroll the list to the bottom.
On subsequent loads, the first item is not the loading indicator, and it doesn't move. So the RecyclerView will not appear to scroll, since it doesn't have to do so to keep the "anchor" on-screen.
My recommendation is to check insertPos and see if it is zero. If it is, that means this is the first load, so you should update the adapter by calling notifyDataSetChanged() in order to avoid this anchoring behavior. Otherwise, call notifyItemRangeInserted() as you're currently doing.
Remove the setAdapter code from onResume ASAP as you are setting new TipsListAdapter(this, tips);
Every time a new reference of the adapter is created...make field mAdapter and then set it in onCreate . RecyclerView doesnt remember the scrolled position because everytime a new reference of adapter is being created.. onResume gets called infinitely when activity is in running state..
So either you setAdapter in onCreate using new operator to create reference for adapter or,
in onResume use mAdapter field variable reference..

Toggle Button State in ViewPager when a page is removed

I have a toggle button (in all pages) to allow users to like the contents of a page (of viewpager).
When user unlikes a page, the page gets removed from the viewpager.
Below is the cycle
User "Unlikes" -> Updates SQLite -> NotifyDatasetChanged() -> Fragments
Rebuilt -> Viewpager displayed
The issue is suppose I am in position 2 and I "unlike" - The page gets removed - in its place a new page is placed with the same toggle button state as that of the removed page, whereas I expect the togglebutton state to be set based on actual value returned by the Cursor.
Even though "isChecked()" status of the ToggleButton is "true" and is being returned in getItem() as a part of rootview - and is also being displayed in a mock TextView I created - Somehow the "Checked" State is retained from the removed page.
Adapter
public class CursorPagerAdapter extends FragmentStatePagerAdapter {
.
.
public Fragment getItem(int position) {
Fragment fragment = null;
if (mCursor.moveToPosition(position)) {
Bundle arguments = new Bundle();
fragment = new myDetailFragment();
fragment.setArguments(arguments);
}
return fragment;
}
}
Population
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ToggleButton addFav = (ToggleButton) rootView.findViewById(R.id.addFavorite);
.
.
if (item_status.equals("0")) {
addFav.setChecked(false);
afw.setBackgroundColor(Color.GRAY);
} else (item_status.equals("1")) {
addFav.setChecked(true);
afw.setBackgroundColor(Color.RED);
}
.
.
addFav.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final Context ctx = getActivity();
ContentValues values = new ContentValues(1);
SharedPreferences prefs = getActivity().getSharedPreferences("com.dap.qgit", Context.MODE_PRIVATE);
ToggleButton tb = (ToggleButton) v;
if (tb.isChecked()) {
values.put(DataProvider.COL_ITEM_STATUS, "1");
} else {
values.put(DataProvider.COL_ITEM_STATUS, "0");
}
String[] args = new String[1];
args[0] = "" + tb.getTag();
ctx.getContentResolver().update(DataProvider.CONTENT_URI_ITEM, values, "item_id=?", args);
getContext().getContentResolver().notifyChange(DataProvider.CONTENT_URI_TAG_ITEMS,null);
}
});
}
There are several things you need to consider when using FragmentStatePagerAdapter implementation with a dynamic data set like Cursor.
Issues
FragmentStatePagerAdapter from the support library maps its instantiated fragments to theirs corresponding positions not to theirs corresponding ids,
FragmentStatePagerAdapter by default does not support dynamically changing data set as you can see by implementation of getItemPosition(Object object) which always returns POSITION_UNCHANGED
Solutions
You need to use implementation of FragmentStatePagerAdapter which maps its instantiated fragments to theirs corresponding ids from the Cursor and is also capable to restore state of those fragments by theirs corresponding ids,
Such adapter implementation also needs to return proper position of a fragment according to position of its associated data in the Cursor via
getItemPosition(Object object) method.
Now, you may implement/port FragmentStatePagerAdapter from the support library, so such implementation provides both, mapping by ids and proper position resolving features or you may use some library which already provides such features for you, like this library which provides the first feature and the second one may be simply implemented by this GitHub Gist.
First please read "Martin Albedinsky" reply - Thanks to him for helping me isolate the issue.
I used the below in my layout XML to circumvent the issue:
<ToggleButton
.
.
.
android:saveEnabled="false"
.
.
.
>
Though I have not personally tried Martin's solution - which by his explanation appears to be a cleaner approach.
I am yet to ascertain if this solution that worked for me causes any performance/untoward issues.

How to update view pager item TITLE dynamically

I have a simple messaging application module. In which, there are two swipable tabs. Received and Sent.
Let us say I have 20 messages out of which 10 are unread. So what I am doing is showing the tabs as Received - (10). Now when I read the message, it marks the message as read. So I would like to change the title from Received - (10) to Received - (9).
Please let me know how can I do it?
Here is the code which I am using.
#Override
public int getCount() {
return 2;
}
#Override
public CharSequence getPageTitle(int position) {
if (position == 0) {
// if position is zero, set the title to RECEIVED.
return "Received" + " (" + String.valueOf(intUnreadReceivedMessagesCount) + ")";
} else {
// if position is 1, set the title to SENT.
return "Sent";
}
}
I am using Pager Sliding Tab Strip as a Pager Tab library. https://github.com/astuetz/PagerSlidingTabStrip
I have tried using notifyDataSetChanged() but for obvious reasons, it does not call it. Any way to resolve the issue. Any better alternative to show and update the count is also welcome.
Thank you.
It's just a guess without seeing the rest of your code.
notifyDataSetChanged() should be working but there is a trick. You have to override one method in your adapter:
public int getItemPosition(Object item) {
return POSITION_NONE;
}
This way calling notifyDataSetChanged() will update currently visible page and it's neighbours.
Without it, only new pages are updated.
Update
I looked at linked library. There is a public method notifyDataSetChanged() just like for adapter. So just assign an id for that PagerSlidingTabStrip inside XML and get a reference in your code. Then call:
adapter.notifyDataSetChanged();
tabStrip.notifyDataSetChanged();
Accepted answer didn't work for me, using a custom PagerAdapter, and android.support.design.widget.TabLayout for the tabs.
This worked for me:
private void refreshTabTitles() {
for (int i = 0; i < adapter.getCount(); i++) {
Tab tab = tabs.getTabAt(i);
if (tab != null) {
tab.setText(adapter.getPageTitle(i));
}
}
}
Using PagerTabStrip:
I did simply a call from inside adapter's InstantiateItem method to
this.GetPageTitleFormatted(position);
and voilá, it worked!

ListView inside ViewPager - Items in ListView keep blank

I'm facing a very strange phenomenon. I'm using a ViewPager from the compatibility package to display profiles.
Every profile is just a ListView with custom elements in it. Every element has two states:
There is data - display it
There's no data - display place holder
Every time I swipe between profiles I reset the data within the item objects. When I remove the REST-Calls and just let the profile item empty - which should display the placeholder - all items keep blank.
What could be the problem?
EDIT:/
That's how I set the current item in the listview. The PageViewAdapter seems to be OK, if I use static content everything works perfect.
final TextView lblTitle = (TextView)convertView.findViewById(R.id.lblTitle);
final LinearLayout llContent = (LinearLayout)convertView.findViewById(R.id.llContent);
final ProfileItem item = getItem(userPosition);
String title = item.getTitle();
if(title == null || title.equals("")) {
lblTitle.setVisibility(View.GONE);
} else {
lblTitle.setText(item.getTitle());
}
if(item.getContent().getParent() != null) {
((ViewGroup)item.getContent().getParent()).removeAllViews();
}
llContent.removeAllViews();
llContent.addView(item.getContent());
The Items look all very similar like that:
#Override
public View getContent() {
if(isTextEmpty()) {
return noTextTherePlaceholder;
} else {
return view;
}
}
The effect keeps the same even if I return just view or noTextTherePlaceholder.
If I would instate new views like a TextView in the getView method everything works as expected.
With view pager you have to get all your data in your Activity.onCreate and then use that data in your ViewPagers adapter's onInstantiateItem.

Categories

Resources