I have been able to use this library (SimpleRecyclerview) to section my real-time data from Firebase Firestore inside my recyclerview. It works as expected, but I haven't gotten it to update the recyclerview in real-time as the data changes inside the Firestore database. I can achieve this using a normal recyclerview but I need the data to be sectioned, hence the library. I checked this and this on stackoverflow but none of the posts address sectioning real-time data that is constantly changing.
Here are my codes:
My simple cell class -
public class UpcomingCell extends SimpleCell<MatchModel, UpcomingCell.ViewHolder>{
public UpcomingCell(#NonNull MatchModel item) {
super(item);
}
#Override
protected int getLayoutRes() {
return R.layout.item_livescore;
}
#NonNull
#Override
protected UpcomingCell.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, #NonNull View cellView) {
return new UpcomingCell.ViewHolder(cellView);
}
#Override
protected void onBindViewHolder(#NonNull UpcomingCell.ViewHolder holder, int position, #NonNull Context context, Object payload) {
holder.visitorTeamName.setText(getItem().getVisitorTeam());
...
...
...
}
public class ViewHolder extends SimpleViewHolder {
TextView liveTextView;
public ViewHolder(#NonNull View itemView) {
super(itemView);
liveTextView = itemView.findViewById(R.id.liveTextView);
...
...
...
}
}
}
Recycler Header method -
public void addRecyclerHeaders(){
SectionHeaderProvider<MatchModel> sectionHeaderProvider = new SimpleSectionHeaderProvider<MatchModel>() {
#NonNull
#Override
public View getSectionHeaderView(#NonNull MatchModel item, int position) {
View view = LayoutInflater.from(getActivity().getApplicationContext()).inflate(R.layout.item_group_header,null,false);
TextView headerTitle = view.findViewById(R.id.headerTitle);
TextView headerCountry = view.findViewById(R.id.headerCountry);
ImageView leagueLogo = view.findViewById(R.id.leagueImage);
headerCountry.setText(item.getGroupCountry());
headerTitle.setText(item.getGroupTitle());
return view;
}
#Override
public boolean isSameSection(#NonNull MatchModel item, #NonNull MatchModel nextItem) {
return item.getGroupID() == nextItem.getGroupID();
}
#Override
public boolean isSticky() {
return true;
}
};
simpleRecyclerView.setSectionHeader(sectionHeaderProvider);
}
Bind data method -
private void bindData(){
bindStandings = finalData;
Collections.sort(bindStandings, new Comparator<MatchModel>() {
#Override
public int compare(MatchModel o1, MatchModel o2) {
return o1.getGroupID() - o2.getGroupID();
}
});
for (MatchModel matchModel : bindStandings){
UpcomingCell upcomingCell = new UpcomingCell(matchModel);
upcomingCell.setOnCellClickListener(new SimpleCell.OnCellClickListener<MatchModel>() {
#Override
public void onCellClicked(#NonNull MatchModel item) {
}
});
cells.add(upcomingCell);
}
simpleRecyclerView.addCells(cells);
}
And this is the method I use in getting data from Firestore -
private void getAllData(){
standings = new ArrayList<>();
Group groupA = new Group(0, "Premier League", "England", 8);
...
...
...
Date todayDate = Calendar.getInstance().getTime();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
String todayString = formatter.format(todayDate);
Query query = firebaseFirestore.collection("fixtures")
.whereEqualTo("real_date", todayString)
.orderBy("league_id")
.orderBy("timestamp");
query.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value, #Nullable FirebaseFirestoreException error) {
if(error != null){
noMatchTextView.setVisibility(View.GONE);
}else{
standings.clear();
finalData.clear();
bindStandings.clear();
cells = new ArrayList<>();
for(DocumentChange documentChange : value.getDocumentChanges()){
if(Integer.parseInt(String.valueOf(documentChange.getDocument().get("league_id"))) == 8){
MatchModel plModel = documentChange.getDocument().toObject(MatchModel.class);
plModel.setGroup(groupA);
standings.add(plModel);
}
...
...
...
}
noMatchTextView.setVisibility(View.GONE);
finalData = standings;
bindData();
}
}
});
}
Please can anyone let me know how to use the library to achieve this, or is there a better/easier way to section real-time Firestore data in a recyclerview? Thank you.
Related
I have 2 view models observing 2 tables in room each emitting live data, they should update my recycler view when a value changes. My adapter is equipped to handle more than one model and view holder, but I'm not sure how to update the recycler views adapter with new data without overwriting the current data or duplicating any data any ideas?
So my adapter takes a list of Visitable (Visitable pattern)
I have 2 objects that implement this interface, the interface has a type so I can tell what view holder it wants and I update the recycler view using diff utils, it look like this
public class CardAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private final List<Visitable> elements;
private final TypeFactory typeFactory;
private final ItemTouchListener onItemTouchListener;
private final Context context;
private String cardType;
private final String layoutIdentifier;
private static final String TAG = "Adptr-Card";
private String CARD_CLICK_UPDATE = "card_click_update";
private final String[] imageFilePathNames;
private RequestManager glide;
public CardAdapter(List<Visitable> elements, TypeFactory typeFactory, ItemTouchListener onItemTouchListener,
Context context,
String cardType, String layoutIdentifier, RequestManager glide) {
this.glide = glide;
this.elements = elements;
this.typeFactory = typeFactory;
this.onItemTouchListener = onItemTouchListener;
this.context = context;
this.cardType = cardType;
this.layoutIdentifier = layoutIdentifier;
this.imageFilePathNames = context.getResources().getStringArray(R.array.image_set_names);
}
#NonNull
#Override
public BaseViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View contactView = LayoutInflater.from(context).inflate(viewType, parent, false);
return typeFactory.createViewHolder(contactView, viewType, onItemTouchListener, glide, cardType);
}
#Override
public void onBindViewHolder(#NonNull BaseViewHolder holder, int position) {
holder.bind(elements.get(position), position);
}
#Override
public int getItemViewType(int position) {
return elements.get(position).type(typeFactory);
}
public void setCardType(String cardType) {
this.cardType = cardType;
notifyDataSetChanged();
}
#Override
public int getItemCount() {
return elements.size();
}
public List<Visitable> getList() {
return elements;
}
public List<Sentence> getSentencesList() {
ArrayList<Sentence> sentences = new ArrayList<>();
for (Visitable visitable : elements) {
if (visitable.type(typeFactory) == CardViewHolder.LAYOUT) {
sentences.add((Sentence) visitable);
}
}
return sentences;
}
public Visitable getItem(int position) {
if (position > 0 && position < elements.size()) {
return elements.get(position);
}
return elements.get(0);
}
class CalculateDiffUtils extends AsyncTask<Void, Void, DiffResult> {
private final List<Visitable> oldCardList;
private final List<Visitable> newCardList;
CalculateDiffUtils(List<Visitable> oldCardList, List<Visitable> newCardList) {
this.oldCardList = oldCardList;
this.newCardList = newCardList;
}
#Override
protected DiffUtil.DiffResult doInBackground(Void... params) {
return DiffUtil.calculateDiff(new VisitableDiffUtils(oldCardList, newCardList, typeFactory));
}
#Override
protected void onPostExecute(DiffUtil.DiffResult diffResult) {
super.onPostExecute(diffResult);
dispatchUpdates(diffResult, newCardList);
}
}
private void dispatchUpdates(DiffUtil.DiffResult diffResult, List<Visitable> newCardList) {
this.elements.clear();
this.elements.addAll(newCardList);
diffResult.dispatchUpdatesTo(this);
}
public void refreshDiffUtilsList(List<Visitable> sentences) {
new CalculateDiffUtils(elements, sentences).execute();
}
public void removeItem(int position) {
elements.remove(position);
notifyItemRemoved(position);
}
public void addCard(Sentence sentence) {
elements.add(getItemCount(), sentence);
notifyItemInserted(getItemCount());
}
public void addGroup(GroupsWithSentences sentence) {
elements.add(getItemCount(), sentence);
notifyItemInserted(getItemCount());
}
public void updateCardClick(int position) {
notifyItemChanged(position, CARD_CLICK_UPDATE);
}
public void refreshList(List<Visitable> newElements) {
ArrayList<Visitable> elementArrayList = new ArrayList<>(newElements);
elements.clear();
elements.addAll(elementArrayList);
notifyDataSetChanged();
}
}
My 2 view models sit in a fragment, they observe some data from my Room database and are updated when changes happen, but this means I will only ever have the data from one of the view models, I guess I want a way to combine these view models maybe using some kind of mediator live data, here are my 2 view models (I've removed stuff for brevity, they are both initiated using factories)
GROUP VIEW MODEL
public class GroupViewModel extends ViewModel {
private final GroupRepository groupRepository;
private final LiveData<List<GroupsWithSentences>> groups;
public GroupViewModel(#NonNull Application application, String[] cardArgs) {
groupRepository = new GroupRepository(application);
groups = groupRepository.getGroupsByWordDescriptionAndWordType(cardArgs[0],cardArgs[1]);
}
public LiveData<List<GroupsWithSentences>> getGroups() {
return groups;
}
}
SENTENCE VIEW MODEL
public class CardViewModel extends ViewModel {
private final SentenceRepository sentenceRepository;
private final LiveData<List<Sentence>> cards;
private static final String TAG = "view_model";
public CardViewModel(#NonNull Application application , int clicks){
sentenceRepository = new SentenceRepository(application);
search = new MutableLiveData<>();
cardArgs = new MutableLiveData<>();
cards = Transformations.switchMap(search, mySearch -> sentenceRepository.searchLiveCardListByWordTypeAndWordDescriptionAndSearchWord(getCardArgs()[0],getCardArgs()[1],mySearch));
}
public LiveData<List<Sentence>> getLiveCardList(){
return cards;
}
}
CALLING ADAPTER IN MY FRAGMENT
private void setUpCardViewModelObserver(String[] args) {
cardViewModel.getLiveCardList().observe(getViewLifecycleOwner(), sentenceList -> {
if (sentenceList != null) {
ArrayList<Visitable> list = new ArrayList<>(sentenceList);
cardAdapter.refreshDiffUtilsList(list);
checkResults(list.size());
}
});
}
private void setUpGroupViewModelObserver() {
groupViewModel.getGroups().observe(getViewLifecycleOwner(), groupsWithSentencesList -> {
if (groupsWithSentencesList != null) {
ArrayList<Visitable> list = new ArrayList<>(groupsWithSentencesList);
cardAdapter.refreshDiffUtilsList(list);
checkResults(groupsWithSentencesList.size());
}
});
}
Any help is welcome, many thanks.
So the answer was to use Mediator Live data, i set the new mediator live data to respond to changes to my existing live data objects and then mediate those changes so i now only have one stream of data so my card view model now looks like this
public CardViewModel(#NonNull Application application , int clicks, String[] cardArgs){
sentenceRepository = new SentenceRepository(application);
search = new MutableLiveData<>();
cards = Transformations.switchMap(search, mySearch -> sentenceRepository.searchLiveCardListByWordTypeAndWordDescriptionAndSearchWord(cardArgs[0],cardArgs[1],mySearch));
groupRepository = new GroupRepository(application);
groups = groupRepository.getGroupsByWordDescriptionAndWordType(cardArgs[0],cardArgs[1]);
sentencesAndGroups = new MediatorLiveData<>();
sentencesAndGroups.addSource(cards, sentences -> {
sentencesAndGroups.setValue(combineLatest(sentences, groups.getValue()));
});
sentencesAndGroups.addSource(groups, groupsWithSentences -> {
sentencesAndGroups.setValue(combineLatest(cards.getValue(), groupsWithSentences));
});
}
and my new combine latest method looks like this
private List<Visitable> combineLatest(List<Sentence> sentenceList, List<GroupsWithSentences> groupsWithSentences) {
List<Visitable> visitableList = new ArrayList<>();
if (sentenceList != null){
visitableList.addAll(sentenceList);
}
if (groupsWithSentences != null){
visitableList.addAll(groupsWithSentences);
}
return visitableList;
}
I'm building a Bank app and want to show history for transactions on the account, When I save the time to Firestore its format as a timestamp, but when I try to display it in my RecyclerView its just seconds and nanoseconds.
How can I show the date and time?
My recyclerView method:
private void setUpRecyclerView() {
String userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
CollectionReference accountTransRef = db.collection(userId).document("accounts")
.collection("accounts").document(accountID).collection("transactions");
Query query = accountTransRef.orderBy("tTimestamp",Query.Direction.DESCENDING);
FirestoreRecyclerOptions<AccountTransactionModel> options = new FirestoreRecyclerOptions.Builder<AccountTransactionModel>()
.setQuery(query, AccountTransactionModel.class)
.build();
adapter = new AccountTransferAdapter(options);
RecyclerView recyclerView = findViewById(R.id.rwTransactionList);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
My Model for Transactions
public class AccountTransactionModel {
private String tType,tAccountToId, tDocumentId;
private Timestamp tTimestamp;
private double tAmount;
public AccountTransactionModel() {
}
public AccountTransactionModel(String tType, String tAccountToId, String tDocumentId, Timestamp tTimestamp, double tAmount) {
this.tType = tType;
this.tAccountToId = tAccountToId;
this.tDocumentId = tDocumentId;
this.tTimestamp = tTimestamp;
this.tAmount = tAmount;
}
public String gettType() {
return tType;
}
public String gettAccountToId() {
return tAccountToId;
}
#Exclude
public String gettDocumentId() {
return tDocumentId;
}
public void settDocumentId(String tDocumentId) {
this.tDocumentId = tDocumentId;
}
public Timestamp gettTimestamp() {
return tTimestamp;
}
public double gettAmount() {
return tAmount;
}
}
My adapter
public class AccountTransferAdapter extends FirestoreRecyclerAdapter<AccountTransactionModel, AccountTransferAdapter.TransferHolder > {
public AccountTransferAdapter(#NonNull FirestoreRecyclerOptions<AccountTransactionModel> options) {
super(options);
}
#Override
protected void onBindViewHolder(#NonNull TransferHolder holder, int position, #NonNull AccountTransactionModel model) {
holder.tvTransListAmount.setText(Double.toString(model.gettAmount()));
holder.tvTransListType.setText(model.gettType());
holder.tvTransListTime.setText(model.gettTimestamp().toString());
}
#NonNull
#Override
public TransferHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.transactions_list,viewGroup,false);
return new TransferHolder(v);
}
class TransferHolder extends RecyclerView.ViewHolder{
TextView tvTransListAmount;
TextView tvTransListTime;
TextView tvTransListType;
public TransferHolder(#NonNull View itemView) {
super(itemView);
tvTransListAmount = itemView.findViewById(R.id.trans_list_amount);
tvTransListTime = itemView.findViewById(R.id.trans_list_time);
tvTransListType = itemView.findViewById(R.id.trans_list_type);
//tvAccName = itemView.findViewById(R.id.tvAccountName);
//tvAccBalance = itemView.findViewById(R.id.tvAccountBalance);
}
}
}
What is displayed in my View,App and Firestore:
Timestamp(seconds=1558437203,nanoseconds=72000000)
If Timestamp is firebase package, then you can go with Timestamp#toDate() function
model.gettTimestamp().toDate().toString() which should give you whole date
Change this:
holder.tvTransListTime.setText(model.gettTimestamp().toString());
into this:
holder.tvTransListTime.setText(model.gettTimestamp().toDate());
From the docs:
public Date toDate ()
Returns a new Date corresponding to this timestamp.
I wanted to know how to load more data in recylcer view using firestore.
Query query = FirebaseFirestore.getInstance()
.collection("ie").limit(5);
adapter=new InterviewAdapter(this,query);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
Adapter class looks like this:
public class InterviewAdapter extends FireStoreAdapter<InterviewAdapter.ViewHolder> {
public interface OnInterviewSelectedListener {
void onInterviewSelected(DocumentSnapshot interview);
}
private InterviewAdapter.OnInterviewSelectedListener mListener;
public InterviewAdapter(Query query, OnInterviewSelectedListener listener) {
super(query);
mListener = listener;
}
#Override
public InterviewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
return new InterviewAdapter.ViewHolder(inflater.inflate(R.layout.ie, parent, false));
}
#Override
public void onBindViewHolder(InterviewAdapter.ViewHolder holder, int position) {
holder.bind(getSnapshot(position), mListener);
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView title,companyName,username,views,isHired;
public ViewHolder(View itemView) {
super(itemView);
title= (TextView) itemView.findViewById(R.id.title);
companyName= (TextView) itemView.findViewById(R.id.companyName);
username= (TextView) itemView.findViewById(R.id.username);
views= (TextView) itemView.findViewById(R.id.views);
isHired= (TextView) itemView.findViewById(R.id.isHired);
}
public void bind(final DocumentSnapshot snapshot,
final OnInterviewSelectedListener listener) {
InterviewExperience experience;
String companyName=snapshot.getString("companyName");
boolean isHired=Boolean.valueOf(snapshot.getBoolean("isHired"));
String username=snapshot.getString("username");
long views=new Double(Double.valueOf(snapshot.getDouble("views"))).longValue();
String id=snapshot.getId();
String title=snapshot.getString("title");
experience=new InterviewExperience(id,title,companyName,username,isHired,views,null,null);
this.title.setText(experience.getTitle());
this.companyName.setText("Company Name: "+experience.getCompanyName());
this.isHired.setText("Hired: "+experience.isHired());
this.views.setText("Views: "+experience.getViews()+"");
this.username.setText("Created By: "+experience.getUsername());
// Click listener
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (listener != null) {
listener.onInterviewSelected(snapshot);
}
}
});
}
}
}
public abstract class FireStoreAdapter<VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH>
implements EventListener<QuerySnapshot> {
private static final String TAG = "FirestoreAdapter";
private Query mQuery;
private ListenerRegistration mRegistration;
private ArrayList<DocumentSnapshot> mSnapshots = new ArrayList<>();
public FireStoreAdapter(Query query) {
mQuery = query;
}
#Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "onEvent:error", e);
onError(e);
return;
}
// Dispatch the event
Log.d(TAG, "onEvent:numChanges:" + documentSnapshots.getDocumentChanges().size());
for (DocumentChange change : documentSnapshots.getDocumentChanges()) {
switch (change.getType()) {
case ADDED:
onDocumentAdded(change);
break;
case MODIFIED:
onDocumentModified(change);
break;
case REMOVED:
onDocumentRemoved(change);
break;
}
}
onDataChanged();
}
public void startListening() {
if (mQuery != null && mRegistration == null) {
mRegistration = mQuery.addSnapshotListener(this);
}
}
public void stopListening() {
if (mRegistration != null) {
mRegistration.remove();
mRegistration = null;
}
mSnapshots.clear();
notifyDataSetChanged();
}
public void setQuery(Query query) {
// Stop listening
stopListening();
// Clear existing data
mSnapshots.clear();
notifyDataSetChanged();
// Listen to new query
mQuery = query;
startListening();
}
#Override
public int getItemCount() {
return mSnapshots.size();
}
protected DocumentSnapshot getSnapshot(int index) {
return mSnapshots.get(index);
}
protected void onDocumentAdded(DocumentChange change) {
mSnapshots.add(change.getNewIndex(), change.getDocument());
notifyItemInserted(change.getNewIndex());
}
protected void onDocumentModified(DocumentChange change) {
if (change.getOldIndex() == change.getNewIndex()) {
// Item changed but remained in same position
mSnapshots.set(change.getOldIndex(), change.getDocument());
notifyItemChanged(change.getOldIndex());
} else {
// Item changed and changed position
mSnapshots.remove(change.getOldIndex());
mSnapshots.add(change.getNewIndex(), change.getDocument());
notifyItemMoved(change.getOldIndex(), change.getNewIndex());
}
}
protected void onDocumentRemoved(DocumentChange change) {
mSnapshots.remove(change.getOldIndex());
notifyItemRemoved(change.getOldIndex());
}
protected void onError(FirebaseFirestoreException e) {};
protected void onDataChanged() {}
}
I used Firestore Adapter code which was given in samples of firestore documentation. Can anyone tell how to use the query object to load more data?
How to load the next 5 items in the recycler view when users scrolls to the end of the list?
You can paginate your Query's result using Query's methods like, startAt(), startAfter(), endAt(), endBefore() with a specified DocumentSnapshot.
If I considered your collection is called "interviews", you can add a method to your FireStoreAdapter like this:
private void paginate(final DocumentSnapshot last, final int limit) {
final Query subset;
if (last == null) {
subset = db.collection("interviews")
.limit(limit);
} else {
subset = db.collection("interviews")
.startAfter(last)
.limit(limit);
}
setQuery(subset);
}
You can perserve the last DocumentSnapshot within onEvent():
final List<DocumentChange> changes = documentSnapshots.getDocumentChanges();
final DocumentSnapshot lastDocument = changes.get(changes.size() - 1).getDocument();
Finally, when users scrolls to the end of the list:
paginate(lastDocument, 5);
And onDocumentAdded() will take care of it. Be carfure NOT to use startAt() because it will not execlude the last one (that already at the end of your list, and will duplicate it).
I am developing an android app that displays the ranks of students based on their marks retrieved from the firebase database. Everything is working fine but, when I update the marks in the db, it keeps the old data and adds the new data in the recyclerView. I can restart the app to refresh the data. But while it is still running, it shows the old data too.
Below is my firebase data:
Student1: {
c: 70,
cPlus: 90,
java: 70,
name: "Samson",
regno: "16sksb7034",
unix: 60
}
Student2: {
c: 20,
cPlus: 85,
java: 68,
name: "Samson",
regno: "16sksb7034",
unix: 86
}
Student3: {
c: 70,
cPlus: 70,
java: 80,
name: "Samson",
regno: "16sksb7034",
unix: 90
}
Here is my dataModel class:
public class Marks {
private String name;
private String regno;
private int c;
private int cPlus;
private int java;
private int unix;
private int percentage;
public Marks() {}
public Marks(int c, int cPlus, int java, int unix) {
this.c = c;
this.cPlus = cPlus;
this.java = java;
this.unix = unix;
}
public int getPercentage() {
return percentage;
}
public void setPercentage(int percentage) {
this.percentage = percentage;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRegno() {
return regno;
}
public void setRegno(String regno) {
this.regno = regno;
}
public int getC() {
return c;
}
public void setC(int c) {
this.c = c;
}
public int getcPlus() {
return cPlus;
}
public void setcPlus(int cPlus) {
this.cPlus = cPlus;
}
public int getJava() {
return java;
}
public void setJava(int java) {
this.java = java;
}
public int getUnix() {
return unix;
}
public void setUnix(int unix) {
this.unix = unix;
}
}
class MarksComparator implements Comparator<Marks> {
#Override
public int compare(Marks marks1, Marks marks2) {
int Marks1Total = marks1.getPercentage();
int Marks2Total = marks2.getPercentage();
if (Marks2Total < Marks1Total) {
return -1;
} else if (Marks2Total > Marks1Total) {
return 1;
} else {
return 0;
}
}
}
Here's my activity class:
public class MarksFragment extends Fragment{
private List<Marks> mMarksList = new ArrayList<>();
private RecyclerView mRecyclerView;
private MyAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private FirebaseDatabase mDatabase;
private DatabaseReference mReference;
private int total=0;
public MarksFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_marks, container, false);
mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
// specify an adapter (see also next example)
/*mAdapter = new MyAdapter(getContext(),mMarksList);
mAdapter.notifyDataSetChanged();
mRecyclerView.setAdapter(mAdapter);*/
//get Firebase Reference
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
mDatabase = FirebaseDatabase.getInstance();
mReference = mDatabase.getReference();
mReference.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return view;
}
public void findPercentage(Marks value) {
total =value.getC() + value.getcPlus() + value.getJava() + value.getUnix();
value.setPercentage(total);
}
private void fetchData(DataSnapshot dataSnapshot) {
Marks value = dataSnapshot.getValue(Marks.class);
Log.v("Marks Fragment", "" +value);
findPercentage(value);
mMarksList.add(value);
Collections.sort(mMarksList, new MarksComparator());
// specify an adapter (see also next example)
mAdapter = new MyAdapter(getContext(),mMarksList);
mAdapter.notifyDataSetChanged();
mRecyclerView.setAdapter(mAdapter);
Here is my adapter class:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder>{
private Context mContext;
private List<Marks> marksList;
public MyAdapter(Context mContext, List<Marks> marksList) {
this.mContext = mContext;
this.marksList = marksList;
}
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView mItemName, mItemRegNo, mItemNo, mTotal;
CircleImageView mImageView;
public MyViewHolder(View view) {
super(view);
mItemName = (TextView) view.findViewById(R.id.card_name);
mItemRegNo = (TextView) view.findViewById(R.id.card_regno);
mItemNo = (TextView) view.findViewById(R.id.item_id);
mImageView = (CircleImageView) view.findViewById(R.id.item_photo);
mTotal = view.findViewById(R.id.card_total);
}
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_item, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
Marks marks = marksList.get(position);
int count = position + 1;
holder.mItemName.setText("" + marks.getName());
holder.mItemRegNo.setText("" + marks.getRegno());
holder.mItemNo.setText("" + count);
holder.mImageView.setImageResource(R.drawable.after_cookie);
holder.mTotal.setText(""+ marks.getPercentage());
}
#Override
public int getItemCount() {
return marksList.size();
}
}
So the code does what its intended to do it retrieves the data and calculates the total and ranks the students. but when I update the data in firebase console the views in recyclerView duplicates temporarily. Like for example if I update Student1 unix value as 10 then two views will be shown in the recyclerView: 1 for previous value and 2 for updated value and again if I update the values it will yet show another views representing the new data without removing the old views. But if I restart recyclerView gets refreshed and its all ok but while I am running the app during the update it shows temporary duplicate views too.
I am new here and this is my first question so I can't even upload picture as you need 10 points to upload photo. I really hope someone help me out on this. I thank you in advance.
UPDATE
Here is link to the image:
When I start the app, the image is:
first Image
when I update the unix value of Student3, the image in recyclerView becomes like this:
After updating the data in firebase console
So, you see it adds new data as well as keeps the old data untill I restart.
Your problem is that you're never checking if the student already exists in your mMarksList so you're simply duplicating him by adding him again with new grades.
What I would do in you case is to add an unique id in firebase to each student.
Then you can check in your fetchData whether the student with that id is already in the array, delete him and add the new one.
private void fetchData(DataSnapshot dataSnapshot) {
Marks value = dataSnapshot.getValue(Marks.class);
Log.v("Marks Fragment", "" +value);
findPercentage(value);
// Get an iterator.
Iterator<Marks> ite = mMarksList.iterator();
while(ite.hasNext()) {
Marks iteValue = ite.next();
if(iteValue.getId().equals(value.getId())) ite.remove();
}
mMarksList.add(value);
....
}
Optionally To make that even cleaner, you can override the equals and hashcode methods in your Marks data model, so that a Marks object is considered the same if the id is equal. More
//ASSUMING THAT ID IS int
#Override
public int hashCode() {
return id;
}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (this.getClass() != obj.getClass()) return false;
Marks other = (Marks) obj;
if (this.getId != other.getId) {
return false;
}
return true;
}
Then it's possible to either use a hashmap, which will override the old student automatically or a arraylist as is and iterate through it before and check if a student equals your new student, like this:
private void fetchData(DataSnapshot dataSnapshot) {
Marks value = dataSnapshot.getValue(Marks.class);
Log.v("Marks Fragment", "" +value);
findPercentage(value);
// Use an iterator.
Iterator<Marks> ite = mMarksList.iterator();
while(ite.hasNext()) {
Marks iteValue = ite.next();
if(iteValue.equals(value)) ite.remove();
}
mMarksList.add(value);
....
}
I have a custom listview to display data from Firebase database.
SectionDetails model
public class SectionDetails {
private String sectionCode;
private String sectionSeats;
public SectionDetails() {
}
public SectionDetails(String sectionCode, String sectionSeats) {
this.sectionCode = sectionCode;
this.sectionSeats = sectionSeats;
}
public String getSectionCode() {
return sectionCode;
}
public String getSectionSeats() {
return sectionSeats;
}
public void setSectionCode(String sectionCode) {
this.sectionCode = sectionCode;
}
public void setSectionSeats(String sectionSeats) {
this.sectionSeats = sectionSeats;
}
}
FirebaseHelper class
public class FirebaseHelper {
DatabaseReference db;
Boolean saved;
ArrayList<SectionDetails> sectionDetailsArrayList = new ArrayList<>();
public FirebaseHelper(DatabaseReference db) {
this.db = db;
}
public Boolean save(SectionDetails sectionDetails) {
if (sectionDetails == null) {
saved = false;
} else {
try {
db.push().setValue(sectionDetails);
saved = true;
adapter.notifyDataSetChanged();
} catch(DatabaseException e) {
e.printStackTrace();
saved = false;
}
}
return saved;
}
private void fetchData(DataSnapshot dataSnapshot) {
sectionDetailsArrayList.clear();
SectionDetails sectionDetails = dataSnapshot.getValue(SectionDetails.class);
sectionDetailsArrayList.add(sectionDetails);
}
public ArrayList<SectionDetails> retrieve() {
db.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
fetchData(dataSnapshot);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return sectionDetailsArrayList;
}
}
CustomAdapter class
public class CustomAdapter extends BaseAdapter {
Context c;
ArrayList<SectionDetails> sectionDetailsArrayList;
public CustomAdapter(Context c, ArrayList<SectionDetails> sectionDetailsArrayList) {
this.c = c;
this.sectionDetailsArrayList = sectionDetailsArrayList;
}
#Override
public int getCount() {
return sectionDetailsArrayList.size();
}
#Override
public Object getItem(int position) {
return sectionDetailsArrayList.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(c).inflate(R.layout.sections_custom_listview, parent, false);
}
TextView lvTvSectionCode = (TextView) convertView.findViewById(R.id.lvTvSectionCode);
TextView lvTvSectionSeats = (TextView) convertView.findViewById(R.id.lvTvSectionSeats);
final SectionDetails sd = (SectionDetails) this.getItem(position);
lvTvSectionCode.setText(sd.getSectionCode());
lvTvSectionSeats.setText("allocated seats: " + sd.getSectionSeats());
convertView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(c, sd.getSectionCode(), Toast.LENGTH_LONG).show();
}
});
return convertView;
}
}
MainActivity class
public class AddSection extends AppCompatActivity implements View.OnClickListener {
DatabaseReference mRef;
FirebaseHelper helper;
CustomAdapter adapter;
Button btnCreateSection;
ListView lvSectionsListOne;
String getFullSection, seats;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_section);
lvSectionsListOne = (ListView) findViewById(R.id.lvSectionsList);
mRef = FirebaseDatabase.getInstance().getReference().child(Constants.FIREBASE_COURSES).child("sections");
helper = new FirebaseHelper(mRef);
adapter = new CustomAdapter(this, helper.retrieve());
lvSectionsListOne.setAdapter(adapter);
btnCreateSection = (Button) findViewById(R.id.btnCreateSection);
btnCreateSection.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (v == btnCreateSection) {
getFullSection = "section 1A";
seats = "20";
SectionDetails sectionDetails = new SectionDetails(getFullSection, seats);
if (helper.save(sectionDetails)) {
adapter = new CustomAdapter(AddSection.this, helper.retrieve());
lvSectionsListOne.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
}
}
I posted a similar question here, but this problem is different. When first data is added, nothing is shown in the listview. When second data is added, first data is shown in listview. And when third data is added, first data is replaced by second data, and second data is shown in listview. I have tried adding adapter.notifyDataSetChanged(), but still the same result.
Data is retrieved (and synchronized) from the Firebase Database to your app asynchronously. This means that your sectionDetailsArrayList may be modified at any time. When you modify the data, you need to tell the adapter about it, so that it can update the view.
private void fetchData(DataSnapshot dataSnapshot) {
sectionDetailsArrayList.clear();
SectionDetails sectionDetails = dataSnapshot.getValue(SectionDetails.class);
sectionDetailsArrayList.add(sectionDetails);
// tell the adapter that we changed its data
adapter.notifyDataSetChanged();
}
This should update the view. But since you clear out the list for every child that gets added or changed, the app will only show the latest added/modified item.
Setting up a synchronized array in Firebase is somewhat involved, since you have to deal with all event types and with the fact that evens can happen in any order. For that reason we create the FirebaseUI library, which contains adapters from the Firebase Database to a ListView and RecyclerView.