I have a fragment with a search bar (edit text) and a recycler view underneath. To display my recycler view I use an adapter. I am trying to display all the friends of a user. The problem is the recycler view items don't appear unless I tap in the search bar (without typing anything).
Furthermore, when loading the images from my database into the image views they get mixed up.
My database is as follows:
{
"Friends": {
"id3":{
"id1": "name1",
"id2": "name2",
...
},
"id4":{
"id1": "name1",
"id3": "name3",
...
}, ...
},
"Users": {
"id1":{
info about user1
},
"id2":{
info about user2
},
"id3":{
info about user3
},
"id4":{
info about user4
}, ...
}
}
I debugged a little bit and found my onCreateViewHolder never gets called when the fragment gets created (not sure if this is relevant).
I also tried to use another query which simply displays all users in the database (so just iterating through the children of the node "Users"). That did work so I imagine the problem is with my queries. However I cannot figure out where I am going wrong.
How I link my recycler view to my adapter:
recyclerView = view.findViewById(R.id.frag_invite_recycler);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
searchBar = view.findViewById(R.id.frag_invite_search_bar);
mUsers = new ArrayList<>();
mAdapter = new EventUserAdapter(getContext(),mUsers);
recyclerView.setAdapter(mAdapter);
My friends query:
Query friends = FirebaseDatabase.getInstance().getReference("Friends").child(firebaseUser.getUid()).orderByValue();
friends.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
mUsers.clear();
for (DataSnapshot snp : dataSnapshot.getChildren()) {
Query users = FirebaseDatabase.getInstance().getReference("Users").child(snp.getKey());
users.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
assert user != null;
mUsers.add(user);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
mAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
My adapter:
#NonNull
#Override
public EventViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(mContext).inflate(R.layout.user_event, viewGroup, false);
return new EventViewHolder(view, mListener);
}
#Override
public void onBindViewHolder(#NonNull EventViewHolder eventViewHolder, int i) {
firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
final User user = mUsers.get(i);
eventViewHolder.username.setText(user.getUsername());
if (user.getImgURL() != null) Glide.with(eventViewHolder.icon.getContext()).load(user.getImgURL()).into(eventViewHolder.icon);
}
#Override
public int getItemCount() {
return mUsers.size();
}
My ViewHolder:
public class EventViewHolder extends RecyclerView.ViewHolder {
public TextView username;
public ImageView check;
public CircleImageView icon;
public EventViewHolder(#NonNull View itemView, final onItemClickListener listner) {
super(itemView);
username = itemView.findViewById(R.id.user_event_name);
icon = itemView.findViewById(R.id.user_event_image);
check = itemView.findViewById(R.id.user_event_check);
}
}
I think the problem is with the location of mAdapter.notifyDataSetChanged(); in My friends query section.
You are calling the command outside of the inner callback from users.addValueEventListener and this might be too early, your users array just been cleared and hasn't been populated yet.
Try to do something like:
friends.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
mUsers.clear();
mAdapter.notifyDataSetChanged(); // for the current views actually reset
// you could also do mAdpater.notifyItemRangeRemoved(0, mUsers.size()) but before clearing the list of course
for (DataSnapshot snp : dataSnapshot.getChildren()) {
Query users = FirebaseDatabase.getInstance().getReference("Users").child(snp.getKey());
users.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
assert user != null;
mUsers.add(user);
mAdapter.notifyItemInserted(mUsers.size() - 1); // to display the current user in the recyclerview
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
If still not working, maybe you're using two different lists of users, one in activity and second in the adapter? check if you are saving the original list's reference or generating new one based on the original's values (at the adpater's constructor). Then, updating the activity's list won't affect the adapter's list.
I hope this will solve your problem!
Related
My problem is that when I click the delete button it deletes the item but not deleting the data in the database.
public class ViewHolderClass extends RecyclerView.ViewHolder {
TextView subjectName, day, time , subCounter;
ImageView delete;
public ViewHolderClass(#NonNull final View itemView) {
super(itemView);
subjectName=itemView.findViewById(R.id.subjectName);
day=itemView.findViewById(R.id.day);
time=itemView.findViewById(R.id.time);
delete = itemView.findViewById(R.id.deleteIcon);
subCounter = itemView.findViewById(R.id.subCounter);
delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int pos = getAdapterPosition();
FetchRheinSubject clickedData = fetchDataList.get(pos);
fetchDataList.remove(pos);
notifyItemRemoved(pos);
notifyItemRangeChanged(pos, fetchDataList.size());
}
});
}
}
If you don't know the key of the items to remove, you will first need to query the database to determine those keys:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
Query applesQuery = ref.child("firebase-test").orderByChild("title").equalTo("Apple");
applesQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot appleSnapshot: dataSnapshot.getChildren()) {
appleSnapshot.getRef().removeValue();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(TAG, "onCancelled", databaseError.toException());
}
});
I am using recyclerview and firebase realtime database together to allow user to send messages to other users.When a message is sent I need to start that activity again to reload the updates.Is there anyway to do this automatically so that when the user clicks on send the message is displayed directly.
Action when send button is clicked
sendButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String message = messageBox.getText().toString();
if (message.equals("")){
Toast.makeText(privatemessageactivity.this, "You cant send an empty message", Toast.LENGTH_SHORT).show();
}else{
messagedref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (!snapshot.exists()){
messagedref.setValue(true);
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
Map<String,Object> mymessage = new HashMap<>();
mymessage.put("Image",myImage);
mymessage.put("Message",message);
sendmessageref.push().setValue(mymessage);
}
}
});
Action to retrieve the data
sendmessageref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (snapshot.exists()){
for (DataSnapshot snapshot1 : snapshot.getChildren()){
MessageClass ld = snapshot1.getValue(MessageClass.class);
list.add(ld);
}
adapter = new MessageAdapter(list);
recyclerView.setAdapter(adapter);
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
My adapter class and my ViewHolder class
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.ViewHolder>{
private List<MessageClass> list;
public MessageAdapter(List<MessageClass> list) {
this.list = list;
}
#NonNull
#Override
public MessageAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.messagelayout,parent,false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull MessageAdapter.ViewHolder holder, int position) {
MessageClass ld = list.get(position);
holder.message.setText(ld.getMessage());
if (!ld.getImage().equals("noimage")){
Glide.with(getApplicationContext()).load(ld.getImage()).into(holder.circleImageView);
}else{
Glide.with(getApplicationContext()).load(R.drawable.profile).into(holder.circleImageView);
}
}
#Override
public int getItemCount() {
return list.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
TextView message;
CircleImageView circleImageView;
public ViewHolder(#NonNull View itemView) {
super(itemView);
message = itemView.findViewById(R.id.messagesmessage);
circleImageView = itemView.findViewById(R.id.messagespp);
}
}
}
The whole purpose of using Firebase is that you don't have to explicitly reload the messages. If you use addValueEventListener instead of addListenerForSingleValueEvent, Firebase will keep listening for changes on the server, and will call your onDataChange again if there as any changes.
So a common way to keep your message list up to date is:
adapter = new MessageAdapter(list);
recyclerView.setAdapter(adapter);
sendmessageref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
list.clear();
for (DataSnapshot snapshot1 : snapshot.getChildren()){
MessageClass ld = snapshot1.getValue(MessageClass.class);
list.add(ld);
}
adapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
throw error.toException();
}
});
The changes:
Uses addValueEventListener instead of addListenerForSingleValueEvent so that it gets called both for the initial data and when there are changes.
Create and assign the adapter outside of onDataChange, since you'll want to set the adapter only once, and then update its data.
Clear the list in onDataChange, since we'll now get called multiple times, and each time we get a full DataSnapshot of all the relevant data.
Notify the adapter of the new data, with adapter.notifyDataSetChanged() so that it can refresh the UI,.
Implement onCancelled, because it's a bad practice to ignore possible errors.
I am new to Firebase and just used it today for our app. I have successfully inserted the items to my Firebase console. Now I want these items to display in my Recyclerview using Fragments.
I have experienced this error since morning and exhausted all the information but nothing seems to work for me.
Please check this code and enlighten me what seems to be the problem:
This my Category model
package com.example.devcash.Object;
public class Category {
public String category_name;
public Category(){
}
public Category(String category_name){
this.category_name = category_name;
}
public String getCategory_name() {
return category_name;
}
public void setCategory_name(String category_name) {
this.category_name = category_name;
}
}
CategoriesFragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_categories, container, false);
//
categoryrecyclerView = (RecyclerView) view.findViewById(R.id.catrecyclerview);
categoryrecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
catlist = new ArrayList<Category>();
reference = FirebaseDatabase.getInstance().getReference().child("Category");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot dataSnapshot1: dataSnapshot.getChildren()){
Category c = dataSnapshot1.getValue(Category.class);
catlist.add(c);
}
adapter = new CategoryAdapter(getContext(), catlist);
categoryrecyclerView.setAdapter(adapter);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Toast.makeText(getContext(), "Something is wrong, please try that again.", Toast.LENGTH_SHORT).show();
}
});
return view;
}
AddCategory
public class AddCategoryActivity extends AppCompatActivity {
private DatabaseReference firebaseDatabase;
private FirebaseDatabase firebaseInstance;
private String CategoryId;
TextInputEditText categoryName;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_category);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
categoryName = (TextInputEditText) findViewById(R.id.text_categoryname);
firebaseInstance = FirebaseDatabase.getInstance();
firebaseDatabase = firebaseInstance.getReference("DataDevcash");
CategoryId = firebaseDatabase.push().getKey();
}
public void addCategory (String categoryName){
Category category = new Category(categoryName);
firebaseDatabase.child("Category").child(CategoryId).setValue(category);
}
public void insertCategory(){
addCategory(categoryName.getText().toString().trim());
Toast.makeText(getApplicationContext(), "Category Successfully Added!", Toast.LENGTH_SHORT).show();
finish();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home){
onBackPressed();
return true;
}else if(id == R.id.action_save){ //if SAVE is clicked
insertCategory();
}
return super.onOptionsItemSelected(item);
}
}
CategoryAdapter
public class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.ViewHolder> {
Context context;
ArrayList<Category> categoryArrayList;
public CategoryAdapter(Context c, ArrayList<Category> categories){
context = c;
categoryArrayList = categories;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.customlayout_category, viewGroup, false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder viewHolder, int i) {
viewHolder.categoryname.setText(categoryArrayList.get(i).getCategory_name());
}
#Override
public int getItemCount() {
return categoryArrayList.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
TextView categoryname;
public ViewHolder(#NonNull View itemView) {
super(itemView);
categoryname = (TextView) itemView.findViewById(R.id.txtcategory_name);
}
}
}
fragment_categories.xml
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/catrecyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerHeight="1dp" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/addcategories_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_add"
android:tint="#color/whiteBG"
android:backgroundTint="#color/colorPrimary"/>
</FrameLayout>
Logcat
RecyclerView: No adapter attached; skipping layout
When I try to move my FirebaseDatabase code from onCreateView to onCreate, I will get this error from my logcat.
Logcat
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView.setLayoutManager(android.support.v7.widget.RecyclerView$LayoutManager)' on a null object reference
at com.example.devcash.Fragments.CategoriesFragment.onCreate
I have also tried to change the layout_height from match_parent to wrap_content and vice versa but still the items will not show..
The first logcat is simply a warning saying that .setAdapter has not been called on your recyclerView hence nothing would be loaded to it. This is probably because the overridden method onDataChange where you added setAdapter trigger was never called.
The second logcat is an error telling you that you tried to access the RecyclerView before it is initialized. Why? Because you are calling from the wrong method block. Check android fragment lifecycle for more info
To fix both issues refactor your code like below
-- Initialize your adapter and recyclerView only once. i.e inside onCreateView block
catlist = new ArrayList<Category>();
adapter = new CategoryAdapter(getActivity(), catlist);
categoryrecyclerView.setAdapter(adapter);
-- Call notifyDataSetChanged on the adapter anytime the value of catlist changes. Add the below code inside onDataChange callback method
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot dataSnapshot1: dataSnapshot.getChildren()){
Category c = dataSnapshot1.getValue(Category.class);
catlist.add(c);
}
adapter.notifyDataSetChanged();
// adapter = new CategoryAdapter(getContext(), catlist);
// categoryrecyclerView.setAdapter(adapter);
}
You can verify if your implementation is right by adding some dummy data to catlist
//call this any where and if it works then be rest assured
//that no data is returned from your firebase reference
catlist.add(new Category("Sample Category"));
adapter.notifyDataSetChanged();
try this
CategoryAdapter adapter = new CategoryAdapter(getContext());
categoryrecyclerView.setLayoutManager(new
LinearLayoutManager(getContext()));
categoryrecyclerView.setAdapter(adapter );
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot dataSnapshot1: dataSnapshot.getChildren()){
Category c = dataSnapshot1.getValue(Category.class);
catlist.add(c);
}
adapter.pushData(catlist);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Toast.makeText(getContext(), "Something is wrong, please try that
again.", Toast.LENGTH_SHORT).show();
}
});
inside your adapter add this method
public void pushData(List<Category> list){
this.list.clear();
this.list.addAll(list);
notifyDataSetChanged();
}
CategoryAdapter adapter = new CategoryAdapter(getactivity(),catlist);
categoryrecyclerView.setLayoutManager(new
LinearLayoutManager(getactivity()));
categoryrecyclerView.setAdapter(adapter );
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
catlist.clear();
for (DataSnapshot dataSnapshot1: dataSnapshot.getChildren()){
Category c = dataSnapshot1.getValue(Category.class);
catlist.add(c);
}
adapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Toast.makeText(getContext(), "Something is wrong, please try that
again.", Toast.LENGTH_SHORT).show();
}
});
change your adapter with attached array list.Get value from it.
I want data from firebase to be printed out in recyclerview list.
The loop is correct but, it didn't replace my data.
This is from the track_record code.
private void readData(){
reference = FirebaseDatabase.getInstance().getReference("FireAlarm");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
dataList.clear();
for (DataSnapshot snapshot : dataSnapshot.getChildren()){
Data data = snapshot.getValue(Data.class);
dataList.add(data);
}
recordAdapter = new RecordAdapter(track_record.this,dataList);
recyclerView.setAdapter(recordAdapter);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
this is from my adapter
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_data,parent,false);
return new RecordAdapter.ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
Data data = dataList.get(position);
holder.outputData.setText(data.getAlarm());
}
#Override
public int getItemCount() {
return dataList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView outputData;
public ViewHolder(View itemView){
super(itemView);
outputData = itemView.findViewById(R.id.dataFetch);
}
}
this is from xml the data should be replaced
<TextView
android:id="#+id/dataFetch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="data"
android:textSize="20dp"
android:textAlignment="center"
android:background="#F5B6B6"
android:textColor="#000000"/>
this is my database
this is current output
You must add child value event listener to FireAlarm, the listener will be triggered as soon as it is attached to a DatabaseReference object (in your code the object is reference) by calling onChildAdded() method and triggered each time data in the child added, removed, changed,...
Your readData should be rewritten as below:
reference.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
Data data = (Data) dataSnapshot.getValue(Data.class);
dataList.add(data);
//then notify data changed
recordAdapter.notifyDataSetChange();
}
#Override
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
Data data = (Data) dataSnapshot.getValue(Data.class);
//then find position of `data` in datalist to change some information of the item at position.
//then notify data changed
recordAdapter.notifyDataSetChange();
}
#Override
public void onChildRemoved(#NonNull DataSnapshot dataSnapshot) {
Data data = (Data) dataSnapshot.getValue(Data.class);
//then find data in datalist to remove, after removed, notify data set changed
recordAdapter.notifyDataSetChange();
}
#Override
public void onChildMoved(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
You should use addChildEventListener instead of addValueEventListener, and write the code inside onChildAdded.
You can find the explanation from documentation:
When working with lists, your application should listen for child
events rather than the value events used for single objects.
databaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (final DataSnapshot post : dataSnapshot.getChildren()) {
FirebaseRecyclerAdapter<GetModel, RecyclerViewAdapter.ViewHolder> mAdapter = new FirebaseRecyclerAdapter<GetModel, RecyclerViewAdapter.ViewHolder>
(GetModel.class, R.layout.rview, RecyclerViewAdapter.ViewHolder.class, query) {
#Override
protected void populateViewHolder(final RecyclerViewAdapter.ViewHolder viewHolder, final GetModel model, final int position) {
viewHolder.textview.setText(model.getName());
}
};
recyclerView.setAdapter(mAdapter);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
This code set
viewHolder.textview.setText(model.getName());
with the last child's name only instead of each child's name.
How to fix this?