I have an activity in Android that takes the result of a web service and puts it into an adapter to display a list of items. I've created a listener interface for this called OnLoadCompleteListener and my listener for this activity looks like
mListener = new OnLoadCompleteListener() {
#Override
public void onPostExecute(RootWebService service) {
if(service instanceof WebService) {
//gets the number of items
int total = ((WebService) service).getCount();
for(int i=0; i<total; ++i) {
//Log.d("ITEM", "inserting " + i);
Item item = ((WebService) service).get(i);
//our adapter class automatically receives items
mAdapter.addItem(item);
}
}
}
};
Now this is the bizarre bit: the listener is getting hit, total is being set to 12 (the number of items I asked for) and then the for loop is being bypassed altogether. The adapter remains empty and the screen displays pretty much nothing.
However, if the commented out Log.d(...) call is made active, the loop works.
Why? The callback is being run from the UI thread, the getCount() method is returning the correct value, and this adapter has worked before. What on earth is happening?
By the way, mAdapter is an instance of a subclass of Adapter I wrote for the purpose, and the addItem(Item) method looks like this:
public synchronized void addItem(Item item) {
mItems.add(item);
notifyDataSetChanged();
}
mItems is exactly what you would expect.
I believe you need to do something like invalidate or something along those lines which pretty much just refreshes the data in the adapter.
I think its this.
myListView.getAdapter().notifyDataSetChanged();
If that doesn't work it could be...
myListView.invalidate();
Related
UPDATE:
I see the same error ("Inconsistency detected. Invalid view holder adapter position") in another situation - this time when bulk adding.
The situation is I am implementing a nested recyclerview, each of which uses a RealmRecyclerViewAdapter and each has an OrderedRealmCollection as its basis. The result I'm going after is this:
I have implemented this at the first level by a query for distinct items in my realm keyed off of year and month:
OrderedRealmCollection<Media> monthMedias = InTouchDataMgr.get().getDistinctMedias(null,new String[]{Media.MEDIA_SELECT_YEAR,Media.MEDIA_SELECT_MONTH});
This gives me one entry for July, one for August, of 2019 in this example.
Then for each ViewHolder in that list, during the bind phase I make another query to determine how many Media items are in each month for that year:
void bindItem(Media media) {
this.media = media;
// Get all the images associated with the year in that date, set adapter in recyclerview
OrderedRealmCollection<Media> medias = InTouchDataMgr.get().getAllMediasForYearAndMonth(null, media.getYear(), media.getMonth());
// This adapter loads the CardView's recyclerView with a StaggeredGridLayoutManager
int minSize = Math.min(MAX_THUMBNAILS_PER_MONTH_CARDVIEW, medias.size());
imageRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(minSize >= 3 ? 3 : Math.max(minSize, 1), LinearLayoutManager.VERTICAL));
imageRecyclerView.setAdapter(new RealmCardViewMediaAdapter(medias, MAX_THUMBNAILS_PER_MONTH_CARDVIEW));
}
At this point I have the single month which is bound to the first ViewHolder, and now I have the count of media for that month, and I want to cause this ViewHolder to display a sampling of those items (maximum of MAX_THUMBNAILS_PER_MONTH_CARDVIEW which is initialized as 5) with the full count shown in the header.
So I pass the full OrderedRealmCollection of that media to the "second level" adapter that handles the list for this CardView.
That adapter looks like this:
private class RealmCardViewMediaAdapter extends RealmRecyclerViewAdapter<Media, CardViewMediaHolder> {
int forcedCount = NO_FORCED_COUNT;
RealmCardViewMediaAdapter(OrderedRealmCollection<Media> data, int forcedCount) {
super(data, true);
this.forcedCount = forcedCount;
}
#NonNull
#Override
public CardViewMediaHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(InTouch.getInstance().getApplicationContext());
View view = layoutInflater.inflate(R.layout.timeline_recycler_row_content, parent, false);
return new CardViewMediaHolder(view);
}
#Override
public void onBindViewHolder(#NonNull CardViewMediaHolder holder, int position) {
// Let Glide load the thumbnail
GlideApp.with(InTouch.getInstance().getApplicationContext())
.load(Objects.requireNonNull(getData()).get(position).getUriPathToMedia())
.thumbnail(0.05f)
.placeholder(InTouchUtils.getProgressDrawable())
.error(R.drawable.ic_image_error)
.into(holder.mMediaImageView);
}
#Override
public int getItemCount() {
//TODO - the below attempts to keep the item count at forced count when so specified, but this is causing
// "Inconsistency detected. Invalid view holder adapter position" exceptions when adding a bulk number of images
return (forcedCount == NO_FORCED_COUNT ? getData().size() : Math.min(forcedCount,getData().size()));
//return getData().size();
}
}
So what this is attempting to do is limit the number of items reported by the adapter to the smaller set of thumbnails to show in the first level CardView to a max of 5, spread around using that StaggeredGridLayout.
All this works perfectly until I do a bulk add from another thread. The use case is the user has selected the FAB to add images, and they have selected a bunch (my test was ~250). Then the Uri for all of this is passed to a thread, which does a callback into the method below:
public void handleMediaCreateRequest(ArrayList<Uri> mediaUris, String listId) {
if ( handlingAutoAddRequest) {
// This will only be done a single time when in autoAdd mode, so clear it here
// then add to it below
autoAddedIDs.clear();
}
// This method called from a thread, so different realm needed.
Realm threadedRealm = InTouchDataMgr.get().getRealm();
try {
// For each mediaPath, create a new Media and add it to the Realm
int x = 0;
for ( Uri uri: mediaUris) {
try {
Media media = new Media();
InTouchUtils.populateMediaFromUri(this, media, uri);
InTouchDataMgr.get().addMedia(media, STATUS_UNKNOWN, threadedRealm);
autoAddedIDs.add(media.getId());
if ( x > 2) {
// Let user see what is going on
runOnUiThread(this::updateUI);
x = 0;
}
x++;
} catch (Exception e) {
Timber.e("Error creating new media in a batch, uri was %s, error was %s", uri.getPath(),e.getMessage());
}
}
} finally {
InTouchDataMgr.get().closeRealmSafely(threadedRealm);
runOnUiThread(this::updateUI);
}
}
This method is operating against the realm, which is then making its normal callback into the OrderedCollection which is the base of the list in the recyclerview(s).
The addMedia() method is standard Realm activity, and works fine everywhere else.
updateUI() essentially causes an adapter.notifyDataSetChanged() call, in this case to the RealmCardViewMediaAdapter.
If I either don't use a separate thread, or I don't attempt to limit the number of items the adapter returns to a max of 5 items, then this all works perfectly.
If I leave the limit of 5 in as the return value from getItemCount() and don't refresh the UI until all has been added, then this also works, even from a different thread.
So it seems there is something about notifyDataSetChanged() being called as the Realm based list of managed objects is being updated in real time that is generating this error. But I don't know why or how to fix?
UPDATE END
I am using Realm Java DB 6.0.2 and realm:android-adapters:3.1.0
I created a class that extends RealmRecyclerViewAdapter class for my RecyclerView:
class ItemViewAdapter extends RealmRecyclerViewAdapter<RealmObject, BindableViewHolder> implements Filterable {
ItemViewAdapter(OrderedRealmCollection data) {
super(data, true);
}
I am initializing this adapter using the standard pattern of passing an OrderedRealmCollection to the adapter:
ItemViewAdapter createItemAdapter() {
return new ItemViewAdapter(realm.where(Contact.class).sort("displayName"));
}
"realm" has been previously initialized in the class creating the adapter.
I allow the user to identify one or more rows in this recyclerView that they want to delete, then I execute an AsyncTask which calls the method handling the delete:
public static class DoHandleMultiDeleteFromAlertTask extends AsyncTask {
private final WeakReference<ListActivity> listActivity;
private final ActionMode mode;
DoHandleMultiDeleteFromAlertTask(ListActivity listActivity, ActionMode mode) {
this.listActivity = new WeakReference<>(listActivity);
this.mode = mode;
}
#Override
protected void onPreExecute() {
listActivity.get().mProgressBar.setVisibility(View.VISIBLE);
}
#Override
protected void onPostExecute(Object o) {
// Cause multi-select to end and selected map to clear
mode.finish();
listActivity.get().mProgressBar.setVisibility(View.GONE);
listActivity.get().updateUI(); // Calls a notifyDataSetChanged() call on the adapter
}
#Override
protected Object doInBackground(Object[] objects) {
// Cause deletion to happen.
listActivity.get().handleMultiItemDeleteFromAlert();
return null;
}
}
Inside handleMultiItemDeleteFromAlert(), since we are being called from a different thread I create and close a Realm instance to do the delete work:
void handleMultiItemDeleteFromAlert() {
Realm handleDeleteRealm = InTouchDataMgr.get().getRealm();
try {
String contactId;
ArrayList<String> contactIds = new ArrayList<>();
for (String key : mSelectedPositions.keySet()) {
// The key finds the Contact ID to delete
contactId = mSelectedPositions.getString(key);
if (contactId != null) {
contactIds.add(contactId);
}
}
// Since we are running this from the non-UI thread, I pass a runnable that will
// Update the UI every 3rd delete to give the use some sense of activity happening.
InTouchDataMgr.get().deleteContact(contactIds, handleDeleteRealm, () -> runOnUiThread(ContactListActivity.this::updateUI));
} finally {
InTouchDataMgr.get().closeRealmSafely(handleDeleteRealm);
}
}
And the deleteContact() method looks like this:
public void deleteContact(ArrayList<String> contactIds, Realm realm, Runnable UIRefreshRunnable) {
boolean success = false;
try {
realm.beginTransaction();
int x = 0;
for ( String contactId : contactIds ) {
Contact c = getContact(contactId, realm);
if (c == null) {
continue;
}
// Delete from the realm
c.deleteFromRealm();
if ( UIRefreshRunnable != null && x > 2 ) {
try {
UIRefreshRunnable.run();
} catch (Exception e) {
//No-op
}
x = 0;
}
x++;
}
success = true;
} catch (Exception e) {
Timber.d("Exception deleting contact from realm: %s", e.getMessage());
} finally {
if (success) {
realm.commitTransaction();
} else {
realm.cancelTransaction();
}
}
Now my problem - when I was doing this work entirely from the UI thread I had no errors. But now when the transaction is committed I am getting:
Inconsistency detected. Invalid item position 1(offset:-1).state:5 androidx.recyclerview.widget.RecyclerView{f6a65cc VFED..... .F....ID 0,0-1440,2240 #7f090158 app:id/list_recycler_view}, adapter:com.reddragon.intouch.ui.ListActivity$ItemViewAdapter#5d77178,
<a bunch of other lines here>
I thought that RealmRecyclerViewAdapter already registered listeners, kept everything straight, etc. What more do I need to do?
The reason I am using a separate thread here is that if a user identifies a few dozen (or perhaps hundreds) items in the list to delete, it can take several seconds to perform the delete (depending on which list we are talking about - there are various checks and other updates that have to happen to preferences, etc.), and I didn't want the UI locked during this process.
How is the adapter getting "inconsistent"?
I solved this by adjusting the architecture slightly. It seems there is some issue with StaggeredGridLayoutManager when mixed with:
The dynamic ability of RealmRecyclerView to update itself automatically during bulk adds or deletes.
An adapter that attempts to limit what is shown by returning a count from getItemCount() that is not equal to the current list count.
I suspect this has to do with how ViewHolder instances get created and positioned by the layout manager, since this is where the error is pointing to.
So what I did instead is rather than have the adapter return a value that can be less than the actual count of the list being managed at any point in time, I changed the realm query to use the .limit() capability. Even when the query returns less than the limit initially, it has the nice side effect of capping itself at the limit requested as the list dynamically grows from the bulk add. And it has the benefit of allowing getItemCount() to return whatever the current size of that list is (which always works).
To recap - when in "Month View" (where I want the user to only see a max of 5 images like the screen shot above), the first step is to populate the adapter of the top level RealmRecyclerView with the result of a DISTINCT type query which results in an OrderedRealmCollection of Media objects which correspond to each month from each year in my media Library.
Then in the "bind" flow of that adapter, the MonthViewHolder performs the second realm query, this time with a limit() clause:
OrderedRealmCollection<Media> medias = InTouchDataMgr.get().getMediasForYearAndMonthWithLimit(null,
media.getYear(),
media.getMonth(),
MAX_THUMBNAILS_PER_MONTH_CARDVIEW); // Limit the results to our max per month
Then the adapter for the RealmRecyclerView associated with this specific month uses the results of this query as its list to manage.
Now it can return getData().size() as the result of the getItemCount() call whether I am in the Month view (capped by limit() ) or in my week view which returns all media items for that week and year.
sorry if this is a convoluted question. Working on creating an app for a college course and I'm running into (what appears to be) a race condition in my OnCreate method.
TL;DR - sometimes my spinner populates and I can get an index from it. Sometimes it's not populated yet when trying to get a specific index. Details and code below.
The app is a "course scheduler" for a college student.
I'm creating an Activity that displays existing course information and allows you to edit it. In the OnCreate method for this Activity, I am filling a spinner for "Mentors" for the course and a spinner for which "Term" the course belongs in. This information is being pulled from a Room DB.
I have a seperate activity for a new course and for editing a course. For the "new course" activity, everything works fine. I getAllMentors() or getAllTerms() successfully and fill the spinner list.
For the "Edit Course" Activity, there's an extra step involved and it seems to be causing me some issues.
When editing a course, I pass the intent from the originating Activity with all the necessary EXTRAS. This is successful.
In OnCreate for EditCourseActivity, I do the following:
I get the mentorID from the EXTRA that's passed in from the originating Activity.
I access my MentorViewModel and call my getAllMentors() method which returns LiveData> of all mentors in the db.
because it returns LiveData, I use an observer and loop through the LiveData adding the Name of each mentor to a List and the
entire mentor to a List.
I populate my spinner with the information in List full of mentor names.
then I do a for loop, looping through List looking for one that has the same id as what I grabbed form the EXTRA in step 1.
If I find a match in that list, I call a getMentorName() method to snag their name as a string.
I have a methond getIndex(spinner, string) that will loop through the provided spinner, trying to find a match for the string that's
passed in (mentors name) that I grabbed that should match the ID of
the mentor assigned to the course. This method returns index location
of the matched string in the spinner.
I set the spinner selection to the index found.
I do basically the same process for term.
Me being a new developer, I'm not used to OnCreate running the code synchronously.
Because of this, it appears that I have a race condition somewhere between populating the List of mentor names that populates the spinner, and calling my getIndex() method.
Sometimes the spinner is populated and getIndex works properly and sets the correct mentor. Sometimes the spinner is empty and my getIndex() returns -1 (which it should do in a no-find situation) that populates the spinner with the first item in the list (once it's populated).
protected void onCreate(Bundle savedInstanceState) {
//////////////////////////Handling Mentor spinner menu/////////////////////////////////////////////////
int mentorId = courseData.getIntExtra(EXTRA_COURSE_MENTOR_ID, -1);
final ArrayAdapter<String> sp_CourseMentorAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mentorNameList);
sp_CourseMentorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sp_CourseMentor.setAdapter(sp_CourseMentorAdapter);
final MentorViewModel mentorViewModel = ViewModelProviders.of(this).get(MentorViewModel.class);
//Mentor test = mentorViewModel.getMentorById(mentorId);
mentorViewModel.getAllMentors().observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
if (mentorList != null) {
for (Mentor m : mentorList) {
mentorNameList.add(m.getMentor_name());
mentorListMentor.add(m);
}
}
sp_CourseMentorAdapter.notifyDataSetChanged();
}
});
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
Is there a way to get them to run asynchronously? Somehow to get the observer doing my getAllMentors() to complete first and populate the spinner, THEN have the for loop run?
Or a better way to handle this?
Thanks in advance.
Room always runs the code on a separated thread, not the Main/UI thread. You can change that behavior with
allowMainThreadQueries()
after initializating your database. This will make the query run first, populate your list and then run your for-loop code. I do not recommend this approach, since it is a bad practice to make queries on the UI thread.
You have two options:
Change your foor loop to a function and call it after adding the values from the observer:
mentorViewModel.getAllMentors().observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
if (mentorList != null) {
for (Mentor m : mentorList) {
mentorNameList.add(m.getMentor_name());
mentorListMentor.add(m);
}
lookForMentor();
}
}
});
private void lookForMentor() {
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
}
Put the for inside the observer, change the Room DAO to return a List and use LiveData on your own viewmodel:
MentorViewModel.java:
MentorViewModel extends ViewModel {
private MutableLiveData<List<Mentor>> _mentorsLiveData = new MutableLiveData<List<Mentor>>();
public LiveData<List<Mentor>> mentorsLiveData = (LiveData) _mentorsLiveData;
void getAllMentors(){
//room db query
_mentorsLiveData.postValue(mentorsList);
}
}
EditActivity.java:
mentorsViewModel.getAllMentors();
mentorViewModel.mentorsLiveData.observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
mentorsListMentor.addAll(mentorList);
sp_CourseMentorAdapter.notifyDataSetChanged();
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
}
}
});
I'm working on a ViewPager fragment that uses FragmentStatePagerAdapter as the adapter. The Adapter's data is a List object that comes from the database, and I am observing the query with MediatorLiveData.
MediatorLiveData merges six different lists that I have for one day and turns them into one list that is used by the Adapter.
I want to add an item to the List to a specific index and update the UI dynamically. The observer notifies the adapter when an item is added and the update works fine, however when I try to do it on a specific index, calling notifyDataSetChanged() causes an IndexOutOfBoundsError.
I initialize the adapter with a list as follows:
public MealDetailsPagerAdapter(FragmentManager fm, List<Long> list, long date, MealViewModel vm) {
super(fm);
this.mealIdList = list;
this.date = date;
this.model = vm;
}
Where MealViewModel is not relevant to this question, it is used on the fragment that the adapter is creating. The list changes are done by another viewmodel.
Here's a code that works correctly:
public void changeItems(List<Long> list, long date) {
if(this.date != date){
this.mealIdList = list;
notifyDataSetChanged();
return;
}
mealIdList.addAll(list);
mealIdList = removeDuplicates(mealIdList);
System.out.println(Arrays.toString(mealIdList.toArray()));
notifyDataSetChanged();
}
And the observer that calls it:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel.getMealIdsInDay().observe(getViewLifecycleOwner(), longs -> {
myAdapter.changeItems(longs, mCurrentIndexDay);
if(isResumed){
mViewPager.setCurrentItem(myAdapter.getPageIndexForMealId(mViewModel.getMealId()));
isResumed=false;
}
updateIndicator(mViewPager);
});
}
Where isResumed is false by default, however if the user adds a new Meal isResumed is changed to true and the viewPager's current position gets changed to the created Meal's position.
However, with the working code, the created Meal's position will always be at the end of the adapter's List because of addAll(). I want to add the meal to a specific position, but if I get the index with mViewPager.getCurrentItem() and send it to the method as follows:
mealIdList.addAll(index, list);
the addAll itself works, but notifyDataSetChanged() causes an IndexOutOfBoundsError.
Here's the complete stack trace:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: fi.seehowyoueat.shye.debug, PID: 14148
java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
at java.util.ArrayList.set(ArrayList.java:453)
at androidx.fragment.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:147)
at androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1212)
at androidx.viewpager.widget.ViewPager.setCurrentItemInternal(ViewPager.java:669)
at androidx.viewpager.widget.ViewPager.setCurrentItemInternal(ViewPager.java:631)
at androidx.viewpager.widget.ViewPager.dataSetChanged(ViewPager.java:1086)
at androidx.viewpager.widget.ViewPager$PagerObserver.onChanged(ViewPager.java:3097)
at androidx.viewpager.widget.PagerAdapter.notifyDataSetChanged(PagerAdapter.java:291)
at fi.octo3.shye.view.viewpagers.MealDetailsPagerAdapter.changeTheItems(MealDetailsPagerAdapter.java:87)
at fi.octo3.shye.fragments.MealDetailsFragment.lambda$onActivityCreated$0(MealDetailsFragment.java:223)
at fi.octo3.shye.fragments.-$$Lambda$MealDetailsFragment$XB4Svnx84FE6kVa5Gzle01e8F3o.onChanged(Unknown Source:4)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at fi.octo3.shye.models.viewmodel.-$$Lambda$2CouvY7DQv4NA0nk6EMoH6jUavw.onChanged(Unknown Source:4)
at androidx.lifecycle.MediatorLiveData$Source.onChanged(MediatorLiveData.java:152)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:149)
at androidx.lifecycle.LiveData.setValue(LiveData.java:307)
at androidx.lifecycle.LiveData$1.run(LiveData.java:91)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:280)
at android.app.ActivityThread.main(ActivityThread.java:6748)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Looking at it, it seems that the problem is somehow caused by the LiveData, but I'm not sure how to fix it. If you want to look at the method that updates the MediatorLiveData that I have it's here:
private void refresh(long key){
if(groupsAndMealsMap.get(key) != null){
//remove source and then value from map
liveDataMerger.removeSource(groupsAndMealsMap.get(key));
groupsAndMealsMap.remove(key);
//add new value to map and add it as a source
groupsAndMealsMap.append(key, mealIdsInGroup);
liveDataMerger.addSource(groupsAndMealsMap.get(key), liveDataMerger::setValue);
}
}
refresh() is called by addItem() that gets an updated List of Meals from the database (list being mealIdsInGroup) and liveDataMerger consists of six LiveData> objects.
Any help will be appreciated. Thank you!
EDIT
Here's the addItem() method, as you can see the service executor waits for the operation to be done before moving on to the refresh() method.
public void addItem(Meal meal, long mealGroupId){
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> {
setMealId(db.mealDao().insertMealIntoGroup(meal, mealGroupId));
mealIdsInGroup = db.mealDao().loadMealsWithinGroup(mealGroupId);
});
service.shutdown();
try {
service.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
refresh(mealGroupId);
}
I suspect that the issue occurs because the insert operation is not over yet.
Use a Handler to put a delay
new Handler(getMainLooper()).postDelayed(new Runnable() {
#Override
public void run() {
notifyDataSetChanged();
}
}, 500);
500 ms just for testing.
Does that issue occurs no matter of what position you're in?
Because if you are in position 0 of the adapter, the adapter didn't created the fragment that's in 3rd position yet.
Remember that the one of the main differences between ViewPagerAdapter and FragmentStatePagerAdapter is that the latter only creates 3 fragments at a time, and if you are in the first position or in the last, the adapter will only contain 2 instances of fragments.
Let me know if this solution helped you!
I need to clear the data of recyclerview because I want to show the fresh data from the server, I dont know how to clear the recyclerview without affecting the infinite scrolling
Just clear your list your list in Adapter and call notifyDataSetChanged method.
public class RecyclerViewAdapter extends RecyclerView.Adapter<RelativeAdapter.ViewHolder> {
private List<model> mArrayList = new ArrayList<>();
public void clearList(){
mArrayList.clear();
notifyDataSetChanged();
}
}
just call the clearList() method when you want to clear list.
I got a solution, the issue was I did not reset EndlessRecyclerOnScrollListener counter variable which was basically calculating the logic for reaching last cardview of recyclerview.
There is another plugin which basically gives you method to reset counter variable
https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView
Hello i have develop an application containing a list with each row a toggle button a text and an image toggle button status either 1 o 0 is stored in isChk integer list of size = number of contacts ... whenever a contact calls the call listener checks if this contact button is pressed or not from the list and perform an action
everything is running quite fine whenever i close the application the calllistener still picks up the call but does not act according to the set buttons it is reset and perform the default action rather than the needed one
how can i keep my applications array available so when the app is closed and the calllistener function picks up the call it acts accordingly?
this is the button array initialization code
public void onClick(View v) {
if(tb.isChecked()){
isChk.set(position,1);
isChkb.set(position,true);
iv.setImageResource(R.drawable.ring);
}else{
isChk.set(position,0);
isChkb.set(position,false);
iv.setImageResource(R.drawable.silentr);
}
and this is the caller function
PhoneStateListener callStateListener = new PhoneStateListener() {
public void onCallStateChanged(int state, String incomingNumber)
{
// React to incoming call.
number=incomingNumber;
// If phone ringing
if(state==TelephonyManager.CALL_STATE_RINGING)
{
String CallerName = getContactDisplayNameByNumber(number);
for (String Curval : myArr){
if (Curval.contains(CallerName)){
found = true;
CallerIndex = (Integer) myArr.indexOf(Curval);
}
}
if (found){
if(isChk.get(CallerIndex)==1){
mAudioManager.setRingerMode(ringerMode1);
}
else{
mAudioManager.setRingerMode(ringerMode2);
}
}
}
if(state==TelephonyManager.CALL_STATE_IDLE)
{mAudioManager.setRingerMode(ringerMode);
}
found = false;
}
};
Storing stuff outside of your app could be what you need for this. Have a look at this
http://developer.android.com/guide/topics/data/data-storage.html#pref
Might be useful.
This is because your settings are not persistent and get lost when app is closed.
As contact list can vary, Use database to store the changes. Check in the database instead of list when call rings.