Android Firebase Database Automatic Refresh UPDATE 2 - java

I have implemented Firebase Database and can successfully add items to it and then display them on a RecyclerView. I also managed to implement Deletion of a child of a database which is successful BUT I need to restart activity to see changes on my phone's screen. For example: when I press Delete on my list item, it disappears from Database instantly but I need to restart the activity to see the change. Here is my code snippets:
private void attachDatabaseReadListener() {
queryRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
locationCurrent = dataSnapshot.getValue(LocationCurrent.class);
locationCurrent.setRefKey(dataSnapshot.getKey());
mLocationAdapter.add(locationCurrent);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
locationCurrent = dataSnapshot.getValue(LocationCurrent.class);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
I believe I should work on onChildRemoved but have no idea where to start. My main Idea was to repopulate recyclerview using for loop but locationCurrent object I got from datasnapshot is null.
Any ideas where should I start looking for solution? I have also considered to run addValueEventListener method on my query but I run into the problem where I get multiple copies of my single child
UPDATE
Referring to some comments here is my adapter
public class LocationAdapter extends ArrayAdapter<LocationCurrent> {
public LocationAdapter(Context context, int resource, List<LocationCurrent> objects) {
super(context, resource, objects);
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = ((Activity) getContext()).getLayoutInflater().inflate(R.layout.item_location, parent, false);
}
LocationCurrent currentLocation = getItem(position);
TextView nameTextView = (TextView) convertView.findViewById(R.id.nameTextView);
TextView descriptionTextView = (TextView) convertView.findViewById(R.id.descriptionTextView);
nameTextView.setText(currentLocation.getName());
descriptionTextView.setText(currentLocation.getDescription());
return convertView;
}
}
By the way - my Ref Key which is used in locationCurrent class is transient variable and thus not visible in Database.
UPDATE2
After all day of working, I still did not manage to get the item off the adapter as soon it is deleted. Instead - I came up with a temporary solution - I added a recreate() method inside my onChildRemoved and it does it's job. (Not a good practice but still - something)

Basically, this is how I did a removal method.
/**
* This removes the CardView from the RecyclerView, allowing us to create a delete request
*
* http://stackoverflow.com/questions/27293960/swipe-to-dismiss-for-recyclerview/30601554#30601554
*
* #param viewHolder
* #param direction
*/
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// First find the position of the card
// https://www.learn2crack.com/2016/02/custom-swipe-recyclerview.html
int position = viewHolder.getAdapterPosition();
// Connect to the database, identify which card was swiped via the RecyclerViewAdapter
DatabaseReference currRef = adapter.getRef(position);
// Get it out of the DB
currRef.removeValue();
}
The following code above should give you a rough idea of the deletion process. You need to retrieve the index of the element you want to remove. In your context, your location key be binded somehow.
Just FYI, here's the populateViewHolder based a FirebaseRecyclerAdapter.
/**
* Populating the RecyclerView..
*
* #param viewHolder
*
*
* #param task
*
*
* #param position
* With the use of position, we can obtain the key of from the FirebaseDatabase
*
* http://stackoverflow.com/questions/37568703/how-to-get-keys-and-values-using-firebaselistadapter
*/
#Override
protected void populateViewHolder(TaskViewHolder viewHolder, Tasks task, int position) {
// Get the key of the Tasks object
//String currentKey = getRef(position).push().getKey();
//final String currentKey = getRef(position).toString(); // This returns the object URL from Firebase
final String currentKey = getRef(position).getKey();
Log.d(TAG, currentKey.toString());
Log.d(TAG, "Image: " + task.getImageUrl());
// Perform some DateTime formatting from the ISO8601 format
// Basically we need to attach the task to the viewHolder so that
// the cards can instantiate their view properly
viewHolder.setTaskName(task.getTaskName());
viewHolder.setTaskDesc(task.getTaskDescription());
viewHolder.setTaskDate(task.getTaskDeadline());
viewHolder.setTaskImage(task.getImageUrl());
final Intent updateView = new Intent(getActivity(), UpdateTaskActivity.class);
// Implement Serializable on the Tasks object,
// Push the object directly via updateView.putExtra
// That way we can have everything we need in the object.
//updateView.putExtra("TaskName", task.getTaskName());
//updateView.putExtra("TaskDesc", task.getTaskDescription());
updateView.putExtra("TaskObject", task);
updateView.putExtra("Key", currentKey);
viewHolder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
/**
* How to provide a foundation to animate cards
*
* http://stackoverflow.com/questions/27300441/how-do-i-expand-cardviews-to-show-more-detail-like-google-keep-cards
*/
//Toast.makeText(getActivity(), currentKey, Toast.LENGTH_LONG).show(); // Test Line to Showcase the Key.
ActivityOptionsCompat options =
ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
v, // The view which starts the transition
getString(R.string.transition_taskcard) // The transitionName of the view we’re transitioning to
);
ActivityCompat.startActivity(getActivity(), updateView, options.toBundle());
}
});
}
Just showing this example to let you know that both your view and your deletion code must be dynamic.
If you're doing a dynamic view instead, you an refer to the documentation here https://github.com/firebase/emberfire
UPDATE Since you just added your adapter, it is also possible to have an onClickListener within getView to perform any removal as such if you intend to.

Related

Switch changes its behaviour when there are more than 5 items in RecyclerView

I have recyclerView showing list of items from database. In every item there is a switch showing value true or false depending on database value.
If you change switch state (e.g turn it on/off) it will update value in database with current boolean. What is the problem, that it works well when there are up to 5 items in RecyclerView. If there are more than 5 items Switches of all items change its state to on/randomly and updating the row in database. When I tested by showing just Toast with current state it all worked fine.
I tested on 2 databases with same result: both sqlite3 with ContentProviders and now I migrated to Room with LiveData.
This is how I set onCheckedChanged in my adapter:
#NonNull
#Override
public AlarmViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.alarm_item, parent, false);
return new AlarmViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull AlarmViewHolder holder, int position) {
//get data for current adapter position
Alarms currentData = mAlarms.get(position);
//check if alarm is valid and then set this value to the switch
holder.isAlarmActiveSwitch.setChecked(currentData.isValid());
new validationUpdater().switchChangedListener
(holder.isAlarmActiveSwitch,
context, currentData.get_id());`
}
I create instance of validationUpdater() class and call the method switchChangedListener, pass holder.isAlarmActiveSwitch as Switch, context and id of row in Database.
switchChangedListener() method:
public void switchChangedListener(#NotNull final Switch switchActive, #NotNull final Context context, final long id) {
switchActive.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(final CompoundButton compoundButton, final boolean b) {
AsyncTask.execute(new Runnable() {
#Override
public void run() {
AlarmDatabase db = AlarmDatabase.getInstance(context);
AlarmsDao dao = db.alarmsDao();
dao.updateAlarm(id, b);
}
});
}
});
}
and #Query from Dao:
#Query("UPDATE alarm_table SET valid = :valid WHERE _id = :id")
void updateAlarm(final long id, final boolean valid);
Could anyone explain me why this doesn't work as supposed only after more than 5 items are displayed?
your problem is row item duplication in recycler view overried the getItemViewType method, hope this works for you.
#Override
public int getItemViewType(int position) {
return position;
}

Shared Element Transition is not exiting properly

I have fragment from which I'm launching activity with shared element transition that has viewpager in it, the enter transition works fine but when i scroll in view pager and finish transition the shared image comes from left side which is not desired it should reposition itself to where it was launched, here is my code:
Intent myIntent = new Intent(getActivity(), EnlargeActivity.class);
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(getActivity(),
imageView,
ViewCompat.getTransitionName(imageView));
startActivity(myIntent, options.toBundle());
I'm updating view and its name in activity that contains viewpager when finishing activity, but its going with blink:
public void finishAfterTransition() {
setEnterSharedElementCallback(new SharedElementCallback() {
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// Clear all current shared views and names
names.clear();
sharedElements.clear();
ViewGroup viewGroup = (ViewGroup) viewPagerDetail.getAdapter()
.instantiateItem(viewPagerDetail, viewPagerDetail.getCurrentItem());
if (viewGroup == null) {
return;
}
// Map the first shared element name to the child ImageView.
sharedElements.put(viewGroup.findViewById(R.id.img).getTransitionName(), viewGroup.findViewById(R.id.img));
// setExitSharedElementCallback((SharedElementCallback) this);
}
});
super.finishAfterTransition();
Basically, Android start the transition with your pre-defined View and transitionName and automatically use the same properties for the return transition. When you change your focused View in ViewPager, Android doesn't know about that and keep the transition on the previous one on its way back. So you need to inform Android about the changes:
Remap the transition properties: Use setEnterSharedElementCallback to change the transitionName and View to the new one before returning from Activity2.
Wait for the Activity1 to finish rendering addOnPreDrawListener.
It's a bit complex in the final implementation. But you can look at my sample code https://github.com/tamhuynhit/PhotoGallery. I try to implement the shared-element-transition from many simple to complex sections.
Your problem appeared from Level 3 and solved in Level 4.
I am writing a tutorial about this but it's not in English so hope the code can help
UPDATE 1: Work flow
Here is how I implement it in my code:
Override finishAfterTransition in Activity2 and call setEnterSharedElementCallback method to re-map the current selected item in ViewPager. Also, call setResult to pass the new selected index back to previous activity here.
#Override
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void finishAfterTransition() {
setEnterSharedElementCallback(new SharedElementCallback() {
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
View selectedView = getSelectedView();
if (selectedView == null)
return;
// Clear all current shared views and names
names.clear();
sharedElements.clear();
// Store new selected view and name
String transitionName = ViewCompat.getTransitionName(selectedView);
names.add(transitionName);
sharedElements.put(transitionName, selectedView);
setExitSharedElementCallback((SharedElementCallback) null);
}
});
Intent intent = new Intent();
intent.putExtra(PHOTO_FOCUSED_INDEX, mCurrentIndex);
setResult(RESULT_PHOTO_CLOSED, intent);
super.finishAfterTransition();
}
Write a custom ShareElementCallback so I can set the callback before knowing which View is going to be used.
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static class CustomSharedElementCallback extends SharedElementCallback {
private View mView;
/**
* Set the transtion View to the callback, this should be called before starting the transition so the View is not null
*/
public void setView(View view) {
mView = view;
}
#Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// Clear all current shared views and names
names.clear();
sharedElements.clear();
// Store new selected view and name
String transitionName = ViewCompat.getTransitionName(mView);
names.add(transitionName);
sharedElements.put(transitionName, mView);
}
}
Override onActivityReenter in Activity1, get the selected index from the result Intent. Set setExitSharedElementCallback to re-map new selected View when the transition begins.Call supportPostponeEnterTransition to delay a bit because your new View may not be rendered at this point. Use getViewTreeObserver().addOnPreDrawListener to listen for the layout changes, find the right View by the selected index and continue the transition supportStartPostponedEnterTransition.
#Override
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onActivityReenter(int resultCode, Intent data) {
if (resultCode != LevelFourFullPhotoActivity.RESULT_PHOTO_CLOSED || data == null)
return;
final int selectedIndex = data.getIntExtra(LevelFourFullPhotoActivity.PHOTO_FOCUSED_INDEX, -1);
if (selectedIndex == -1)
return;
// Scroll to the new selected view in case it's not currently visible on the screen
mPhotoList.scrollToPosition(selectedIndex);
final CustomSharedElementCallback callback = new CustomSharedElementCallback();
getActivity().setExitSharedElementCallback(callback);
// Listen for the transition end and clear all registered callback
getActivity().getWindow().getSharedElementExitTransition().addListener(new Transition.TransitionListener() {
#Override
public void onTransitionStart(Transition transition) {}
#Override
public void onTransitionPause(Transition transition) {}
#Override
public void onTransitionResume(Transition transition) {}
#Override
public void onTransitionEnd(Transition transition) {
removeCallback();
}
#Override
public void onTransitionCancel(Transition transition) {
removeCallback();
}
private void removeCallback() {
if (getActivity() != null) {
getActivity().getWindow().getSharedElementExitTransition().removeListener(this);
getActivity().setExitSharedElementCallback((SharedElementCallback) null);
}
}
});
// Pause transition until the selected view is fully drawn
getActivity().supportPostponeEnterTransition();
// Listen for the RecyclerView pre draw to make sure the selected view is visible,
// and findViewHolderForAdapterPosition will return a non null ViewHolder
mPhotoList.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
mPhotoList.getViewTreeObserver().removeOnPreDrawListener(this);
RecyclerView.ViewHolder holder = mPhotoList.findViewHolderForAdapterPosition(selectedIndex);
if (holder instanceof ViewHolder) {
callback.setView(((ViewHolder) holder).mPhotoImg);
}
// Continue the transition
getActivity().supportStartPostponedEnterTransition();
return true;
}
});
}
UPDATE 2: getSelectedItem
To get selected View from the ViewPager, don't use getChildAt or you get the wrong View, use findViewWithTag instead
In the PagerAdapter.instantiateItem, use position as tag for each View:
#Override
public View instantiateItem(ViewGroup container, int position) {
// Create the View
view.setTag(position)
// ...
}
Listen to onPageSelected event to get the selected index:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
mSelectedIndex = position;
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
Call getSelectedView to get the current view by the selected index
private View getSelectedView() {
try {
return mPhotoViewPager.findViewWithTag(mSelectedIndex);
} catch (IndexOutOfBoundsException | NullPointerException ex) {
return null;
}
}
This is actually a default behavior, I was struggling SharedElementTransitions a lot, but I have nested fragments. I got my solution from an article (very recent article), it shows an implementation with a RecyclerView, which I assume you have. In short, the solution is to override onLayoutChange :
recyclerView.addOnLayoutChangeListener(
new OnLayoutChangeListener() {
#Override
public void onLayoutChange(View view,
int left,
int top,
int right,
int bottom,
int oldLeft,
int oldTop,
int oldRight,
int oldBottom) {
recyclerView.removeOnLayoutChangeListener(this);
final RecyclerView.LayoutManager layoutManager =
recyclerView.getLayoutManager();
View viewAtPosition =
layoutManager.findViewByPosition(MainActivity.currentPosition);
// Scroll to position if the view for the current position is null (not
// currently part of layout manager children), or it's not completely
// visible.
if (viewAtPosition == null
|| layoutManager.isViewPartiallyVisible(viewAtPosition, false, true)){
recyclerView.post(()
-> layoutManager.scrollToPosition(MainActivity.currentPosition));
}
}
});
Here is the article, and you will also find the project on GitHub.

Realm calling onChange addChangeListener continuously

Apologies if this is poorly explained, I am having difficulty in understanding it myself. If you point out anything you don't understand I will do my best to correct any issues. Okay so here we go.
Several classes. (D&D sheet, sheet has weapons user can equip, this is about equipping said weapons which is stored in a list)
A fragment activity - CombatFragment
The arrayadapter list which is declared in CombatFragment -
AttackListViewContentAdapter
The realm object - Weapon
The realm object where a list of Weapon is held - Sheet
A number of XML files (The code of which I won't paste here as SO has a limit on code. content_combat, attack_list_item
What I've gathered so far is that when I create a new attackListViewContentAdapter it loops at a rapid and continued pace. So much so that the screen does not respond to me touching any of the widgets. I've done things like log a number each time it passes so it shows when it's doing it again and again. If you need information on that I can show you where I put the logs and what shows in my Logcat when I add an additional view (row).
I believe that it's something to do with the onChangedListener which keeps being triggered, even if I found the reason why how do I then get to a stage where I can create a new view and have the listener so it can record changes.
Please note in the interests of space I will be using abbreviated code. I've ignored things like dialog boxes and widgets which aren't relevant. So if it seems like something missing or you need to view the classes, it's possibly in the file which I've linked above each one.
CombatFragment
public class CombatFragment extends Fragment {
#BindView(R.id.lv_attack_spellcasting_content)
ListView lv_attack_spellcasting_title;
#Nullable
#Override
public View onCreateView(final LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.content_combat, container, false);
RealmList<Weapon> weaponList = sheet.getWeaponList();
final AttackListViewContentAdapter attackListViewContentAdapter = new AttackListViewContentAdapter(getActivity(), sheet, realm, weaponList);
weaponList.addChangeListener(new RealmChangeListener<RealmList<Weapon>>() {
#Override
public void onChange(RealmList<Weapon> weapons) {
/* Gives the adaptor a kick to know that the weapon realm list has changed */
attackListViewContentAdapter.notifyDataSetChanged();
loopOnChanged++;
}
});
lv_attack_spellcasting_title.setAdapter(attackListViewContentAdapter);
playerInit();
return rootView;
}
// This is a fake method, this is just to show that the .add is in it's own method which is triggered by a button press and not in onCreate
public void buttonPress() {
sheet.getWeaponList().add(realm.createObject(Weapon.class));
}
} `
AttackListViewContentAdapter
public class AttackListViewContentAdapter extends ArrayAdapter<Weapon> {
public AttackListViewContentAdapter(Context context, Sheet sheet, Realm realm, List<Weapon> weaponList) {
super(context, 0, weaponList);
this.sheet = sheet;
this.realm = realm;
}
#Override
#NonNull
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
//Because you're returning the view (AttachToRoot is false) the ArrayAdaptor (This class) will handle adding the view to the list.
convertView =
LayoutInflater.from(getContext()).inflate(R.layout.attack_list_item, parent, false);
return convertView;
}
}
Weapon
public class Weapon extends RealmObject {
#PrimaryKey
int weaponID;
//properties, set get methods etc.
}
Sheet
public class Sheet extends RealmObject {
#PrimaryKey
private int sheetID;
private RealmList<Weapon> weaponList;
public RealmList<Weapon> getWeaponList() {
return weaponList;
}
public void setWeaponList(RealmList<Weapon> weaponList) {
this.weaponList = weaponList;
}
}
content_combat
<ListView
android:id="#+id/lv_attack_spellcasting_content"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:columnCount="7"
android:rowCount="1" />
attack_list_item
Nothing really in there to include
Your problem is happening because of bad initialization of your spinner widgets within AttackListViewContentAdapter class.
You must set your spinners selection before setOnItemSelectedListener is set.
You must check whether your spinner selection is not equal to current selection, to avoid an infinite loop between onChange and onItemSelected methods. I mean, your spinners onItemSelected callbacks, execute a realm transanctions, then, those transanctions fire your onChange callback and finally, your onChange callback invokes notifyDataSetChanged() which make cycle start again going into an infinite loop.
To solve your problem, you should follow the next steps inside AttackListViewContentAdapter.java:
A) Remove the following lines from addWeaponToUI() method:
private void addWeaponToUI() {
et_name_value.setText(weapon.getWeaponName());
np_damage_number_of_die_value.setValue(weapon.getWeaponDamageNumberOfDie());
SheetEnum.Ability ability = SheetEnum.Ability.getEnumValue(weapon.getWeaponAbilityBonusInt());
tv_attack_bonus_value.setText(String.valueOf(sheet.getAbilityBonus(ability)));
// REMOVE below lines!
//s_damage_die_type_value.setSelection(weapon.getWeaponDamageDieTypeInt());
//s_damage_type_value.setSelection(weapon.getWeaponDamageTypeInt());
//s_ability_bonus_value.setSelection(weapon.getWeaponAbilityBonusInt());
}
B) Invoke spinner setSelection() before setOnItemSelectedListener(), then check selected item is not equal to selected position to avoid an infinite loop:
ArrayAdapter<CharSequence> damageDieTypeAdapter = ArrayAdapter.createFromResource(getContext(), R.array.die_type, android.R.layout.simple_spinner_dropdown_item);
s_damage_die_type_value.setAdapter(damageDieTypeAdapter);
//Set selection before listener
s_damage_die_type_value.setSelection(weapon.getWeaponDamageDieTypeInt());
s_damage_die_type_value.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View v, final int position, long id) {
//Check selected position is not equal to current position to avoid an infinite loop
if (position != weapon.getWeaponDamageDieTypeInt()) {
String[] value = getContext().getResources().getStringArray(R.array.die_type);
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
weapon.setWeaponDamageDieType(position);
}
});
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
C) Repeat Step B for s_damage_type_value and s_ability_bonus_value spinners

Selected items in RecyclerView change on scrolling

I have a RecyclerView with each element representing an event. I want to let the user select events by clicking it. Once selected, the event(s) and a report button will be colored:
UI before performing a click: click here.
UI After performing a click: click here.
It's pretty simple and allegedly works; I set an OnClickListener for each ViewHolder which is responsible for coloring the item, and when fired it's triggering another event in the owning activity named onOccurrenceSelected, which is responsible for changing the button's state.
However, when scrolling through the RecyclerView's items, other irrelevant items are colored like their OnClickListener was triggered (though it wasn't), and when scrolling back the selected event is colored as not selected. While this is happening, the only event that's supposed to color the items is not triggered.
Any explanation for such behavior? Thanks!
EDIT: Here are some relevant code from the adapter:
private List<Occurrence> mDataSet;
private Activity activity;
public <OccurrencesActivity extends OnOccurrenceSelectedListener> OccurrencesAdapter(OccurrencesActivity occurrencesActivity, List<Occurrence> occurrences) {
this.activity = (android.app.Activity) occurrencesActivity;
mDataSet = occurrences;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Occurrence instance = mDataSet.get(position);
...
setOnClickListener(holder, instance);
}
private void setOnClickListener(final ViewHolder holder, final Occurrence occurrence) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (!occurrence.isSelected()) {
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
} else {
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}
occurrence.setSelected(!occurrence.isSelected());
((OnOccurrenceSelectedListener)activity).onOccurrenceSelected(mDataSet);
}
});
}
Recyclerview always resuse views while scrolling so you have to store selected positions into temporary arraylist and then keep condition check into onBindViewHolder that whether that particular position is already exists in arraylist or not? I updated your adaper. find the below changes with comment
private List<Occurrence> mDataSet;
private Activity activity;
//Added here temporary ArrayList
private ArrayList<String> mSelectedPosition = new ArrayList<String>;
public <OccurrencesActivity extends OnOccurrenceSelectedListener> OccurrencesAdapter(OccurrencesActivity occurrencesActivity, List<Occurrence> occurrences) {
this.activity = (android.app.Activity) occurrencesActivity;
mDataSet = occurrences;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
//Set ViewTag
holder.itemView.setTag(position);
//Check everyposition during view binding process
if(mSelectedPosition.contains(String.valueOf(position))){
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}else{
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}
Occurrence instance = mDataSet.get(position);
...
setOnClickListener(holder, instance);
}
private void setOnClickListener(final ViewHolder holder, final Occurrence occurrence) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Get Position
int position = (int) view.getTag();
//Remove SelectedPosition if Already there
if(mSelectedPosition.contains(position))
mSelectedPosition.remove(String.valueOf(position));
else
mSelectedPosition.add(String.valueOf(position));
notifyDataSetChanged();
//Not sure about this lines
occurrence.setSelected(!occurrence.isSelected());
((OnOccurrenceSelectedListener)activity).onOccurrenceSelected(mDataSet);
}
});
}
Its the default behaviour of recyclerview. it will recycle/reuse views which are not in use currently. If you want to save the state which is colored or not. Then save a parameter in your List<Object> per position. and as per position in onBindViewHolder method use that position to change the color.
Try by Setting Tag to your item in onBindViewHolder of Adapter
holder.yourItem.setTag(position);
And then Inside the onClickListener,Just save that position in shared Pref. if it's selected, whenever you set adapter then before setting values just check that is it selected or not based on shared Pref. and perform action for same.
public void onClick(View view) {
if (!occurrence.isSelected()) {
//save position in share pref.
}
}

What is notifyItemRangeChanged(0, this.data.size()); in this example and how does it work?

I understand how a ViewHolder's onBindViewHolder works, however I'm unclear about how notifyItemRangeChanged(0, this.data.size()); works in this example and what it does exactly.
The data that is supplied to this adapter is in Json format.
The adapter is below:
public class AdapterQuestion extends RecyclerView.Adapter<AdapterQuestion.ViewQuestion>{
private LayoutInflater mLayoutInflater;
//this is an arrayList of questionData objects
private ArrayList<QuestionData> data =new ArrayList<>();
//Created the layoutInflator
public AdapterQuestion(Context context){
//get from context
mLayoutInflater=LayoutInflater.from(context);
}
public void setBloglist(ArrayList<QuestionData> data){
this.data =data;
notifyItemRangeChanged(0, this.data.size());
}
#Override
public ViewQuestion onCreateViewHolder(ViewGroup parent, int viewType) {
//inflates the customQuestion view or converts it to java code
View view= mLayoutInflater.inflate(R.layout.customquestion, null);
//We now want to convert the View into a ViewQuestion, view Question takes
//a view so we pass the view into view question and then return it.
ViewQuestion holder=new ViewQuestion(view);
return holder;
}
//ViewGroup parent and ViewType are not being assigned.
#Override
public void onBindViewHolder(ViewQuestion holder, int position) {
//here we need to bind the data to our view, there is currently no Data!
//We need to get the data from our JSON
//Parameters is a ViewHolder and a Position
//This gives us the current information object from the whole arraylist
//data.get(position) data is the arraylist and we are getting the current position or index;
//That current obj is of Type QuestionData
QuestionData currentObj= data.get(position);
//we are accessing the Inflated view, or saved view with holder
//holder.answerText is the textView in holder. We are then taking that current object
//We are getting the text of the current object and setting it to the AnswerText view
holder.answerText.setText(currentObj.getMtext());
holder.answerId.setText(currentObj.getId());
holder.mVotes.setText(currentObj.getVotes());
holder.mLikeButton.setTag(currentObj);
}
#Override
public int getItemCount() {
return data.size();
}
public class ViewQuestion extends RecyclerView.ViewHolder{
//once we create it once the reclycer view will automatically recycle it
private TextView answerText;
private TextView answerId;
private TextView mVotes;
private LikeButton mLikeButton;
public ViewQuestion (View itemView){
super(itemView);
//here we are finding the views by their ID
answerText=(TextView)itemView.findViewById(R.id.answerText);
answerId=(TextView)itemView.findViewById(R.id.answerId);
mVotes=(TextView)itemView.findViewById(R.id.VoteTextView);
mLikeButton= (LikeButton)itemView.findViewById(R.id.heart_buttons);
mLikeButton.setOnLikeListener(new OnLikeListener() {
#Override
public void liked(LikeButton likeButton) {
Voting vote = new Voting();
vote.onUpVote(convertToString(),
getAdapterPosition(),ViewQuestion.this);
System.out.print("Adapter Position"+getAdapterPosition());
}
#Override
public void unLiked(LikeButton likeButton) {
Voting onDown=new Voting();
onDown.onDownVote(convertToString(),
getAdapterPosition(), ViewQuestion.this);
}
});
}
public String getVoteView(){
String voteView=mVotes.getText().toString();
return voteView;
}
public String convertToString(){
String converted=answerId.getText().toString();
return converted;
}
public int convertToInt(){
String converted=answerId.getText().toString();
int ConvertedInt=Integer.parseInt(converted);
return ConvertedInt;
}
}
}
When the data that is to be set in RecyclerView is changed, the Adapter needs to get notified of the data change so that it can change the data in recyclerview.
The method
notifyItemRangedChanged(fromIndex,toIndex);
is used to notify the adapter that some set of data is changed among the whole data and it tells the adapter that adapter should refresh the data and reload it into the recyclerView starting from fromIndex to toIndex as passed into the method .
use this method if you have multiple data changed but not all , those changed data also are in cluster so that you can say from 5th to 10th index data are changed .
If all data are changed call :
notifyDataSetChanged();
if only one dataItem is changed then call :
notifyItemChanged(dataPosition);
Using notifyItemRangeChanged(0, this.data.size()) it’s bad practice.
Best way is using notifyItemChanged or notifyItemRangeChanged with payload.
Payload - optional parameter (key). That give you opportunity to check what kind of update do you need.
public void onBindViewHolder(/*...*/, List payloads) {
if (payloads.isEmpty()) {
setText(holder, position);
downloadBitmap(holder, position);
} else if (payloads.contains(SET_ONLY_TEXT)){
setText(holder, position);
}
}
In this example payloads used for checking when adapter should update only the text.
in your case you are not doing it(notifyItemRangeChanged) right as you might as well can call notifyDataSetChanged(); because you are telling the adapter that the entire list has changed and not specific position.

Categories

Resources