0
votes

For months I have created a chat for the Android system using Firebase as a database. I followed this guide here ( link ) and at the beginning it was all right, the chat was good and it didn't have any kind of delays. I then started to add other particularities, such as the display or not of the message and the status of the participants (online and offline) and from that moment three problems began to manifest in particular:

1) when I change the chat activity to go to another and then return to the chat, the layout appears empty without messages and if you try to change the activity the application closes itself. I found out that I get this error:

E/RecyclerView: No adapter attached; skipping layout

These are the files that make up the part related to chat:

MessageChat.java

public class MessageChat {

private String sender;
private String receiver;
private String msg;
private String currenttime;
private boolean isseen;

public MessageChat(String sender, String receiver, String msg, String currenttime, boolean isseen){
    this.sender = sender;
    this.receiver = receiver;
    this.msg = msg;
    this.currenttime = currenttime;
    this.isseen = isseen;
}

public MessageChat(){}

public String getSender() {
    return sender;
}

public void setSender(String sender) {
    this.sender = sender;
}

public String getReceiver() {return receiver;}

public void setReceiver(String receiver) { this.receiver = receiver;}

public String getMsg() {
    return msg;
}

public void setMsg(String msg) {
    this.msg = msg;
}

public String getCurrenttime() {
    return currenttime;
}

public void setCurrenttime(String currenttime) {
    this.currenttime = currenttime;
}

public boolean isIsseen() {return isseen;}

public void setIsseen(boolean isseen) {this.isseen = isseen;}
}

msgAdapter.java

public class msgAdapter extends RecyclerView.Adapter<msgAdapter.MsgViewHolder> {

public static final int INT_TYPE_LEFT = 0;
public static final int INT_TYPE_RIGHT = 1;

private static List<MessageChat> mChat;
private static Context context;

private FirebaseUser fuser;

public msgAdapter(List<MessageChat> msg, Context context) {
    this.mChat = msg;
    this.context = context;
}

@Override
public msgAdapter.MsgViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == INT_TYPE_RIGHT) {
        View view = LayoutInflater.from(context).inflate(R.layout.right_message, null);
        msgAdapter.MsgViewHolder msgViewHolder = new msgAdapter.MsgViewHolder(view);
        return msgViewHolder;
    }else{
        View view = LayoutInflater.from(context).inflate(R.layout.left_message, null);
        msgAdapter.MsgViewHolder msgViewHolder = new msgAdapter.MsgViewHolder(view);
        return msgViewHolder;
    }
}

@Override
public void onBindViewHolder(final msgAdapter.MsgViewHolder holder, final int position) {
    MessageChat msg = mChat.get(position);
    holder.show_msg.setText(msg.getMsg());

    if ((position == mChat.size()-1) && msg.getSender().equals(fuser.getUid())){
        if (msg.isIsseen()){
            holder.tv_seen.setText(" Seen ");
            holder.tv_seen.setVisibility(View.VISIBLE);
        }else {
            holder.tv_seen.setText(" Sent ");
            holder.tv_seen.setVisibility(View.VISIBLE);
        }
    }else{
        holder.tv_seen.setVisibility(View.GONE);
    }
}

@Override
public int getItemCount() {
    return mChat.size();
}

public static class MsgViewHolder extends RecyclerView.ViewHolder {
    TextView username, show_msg, tv_seen;
    public MsgViewHolder(final View itemView) {
        super(itemView);
        show_msg = (TextView) itemView.findViewById(R.id.show_msg);
        tv_seen = (TextView) itemView.findViewById(R.id.tv_seen);
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // item clicked
                MessageChat msg = mChat.get(getAdapterPosition());
                String ctime = msg.getCurrenttime();
                TastyToast.makeText(context, ctime, TastyToast.LENGTH_LONG, TastyToast.INFO);
            }
        });
    }
}

@Override
public int getItemViewType(int position) {
    fuser = FirebaseAuth.getInstance().getCurrentUser();
    if (mChat.get(position).getSender().equals(fuser.getUid())){
        return INT_TYPE_RIGHT;
    }else{
        return INT_TYPE_LEFT;
    }
}
}

On the internet I have found many answers that solve this problem in the following way, replacing this:

View view = LayoutInflater.from(context).inflate(R.layout.right_message, null);

to this:

View view = LayoutInflater.from(context).inflate(R.layout.right_message, parent, false);

But in my case I only got the busted chat with empty spaces between one comic and another.

Homefragment.java

public class HomeFragment extends Fragment {

private HomeViewModel homeViewModel;

private View v;
private ImageButton btn_send;
private EditText et_send_mex;

private DatabaseReference reference;

private msgAdapter mAdapter;
private List<MessageChat> mChat;
private RecyclerView recyclerView;

private FirebaseUser user;
private String Uid, Oid;

private static final String sId = "xyz1";
private static final String pId = "xyz2";


ValueEventListener seenListener;

@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    homeViewModel =
            ViewModelProviders.of(this).get(HomeViewModel.class);

    // Inflate the layout for this fragment
    v = inflater.inflate(R.layout.fragment_home, container, false);
    btn_send = v.findViewById(R.id.btn_send);
    et_send_mex = v.findViewById(R.id.et_send_mex);

    recyclerView = v.findViewById(R.id.rv_mex);
    recyclerView.setHasFixedSize(true);
    LinearLayoutManager llManager = new LinearLayoutManager(getContext());
    llManager.setStackFromEnd(true);
    recyclerView.setLayoutManager(llManager);

    user = FirebaseAuth.getInstance().getCurrentUser();
    Uid = user.getUid();
    btn_send.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            // notify = true;
            String m = et_send_mex.getText().toString();
            if (!m.equals("") || !m.equals("\n")){
                if (Uid.equals(pId)){
                    sendMessage(Uid,sId,m);
                }else if (Uid.equals(sId)){
                    sendMessage(Uid,pId,m);
                }
            }
        }
    });
    readMessage();
    return v;
}


private void seenMessage(final String senderId){
    reference = FirebaseDatabase.getInstance().getReference("chats");
    seenListener = reference.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            for (DataSnapshot snapshot: dataSnapshot.getChildren()){
                MessageChat msgchat = snapshot.getValue(MessageChat.class);
                if (msgchat.getReceiver().equals(user.getUid()) && msgchat.getSender().equals(senderId)){
                    HashMap<String, Object> hashMap = new HashMap<>();
                    hashMap.put("isseen", true);
                    snapshot.getRef().updateChildren(hashMap);
                }
            }
        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {

        }
    });
}

private void sendMessage(String sender, String receiver, String message){

    reference = FirebaseDatabase.getInstance().getReference();
    HashMap<String, Object> hashMap = new HashMap<>();
    final Calendar c = Calendar.getInstance();
    int year = c.get(Calendar.YEAR);
    int month = c.get(Calendar.MONTH);
    month = month+1;
    int day = c.get(Calendar.DAY_OF_MONTH);
    int hour = c.get(Calendar.HOUR_OF_DAY);
    int sec = c.get(Calendar.MINUTE);
    String dt = day+" - "+month+" - "+year+", "+hour+":"+sec;
    hashMap.put("currenttime", dt);
    hashMap.put("sender", sender);
    hashMap.put("receiver", receiver);
    hashMap.put("msg", message);
    hashMap.put("isseen", false);
    reference.child("chats").push().setValue(hashMap);
    et_send_mex.setText("");

}

private void readMessage (){

    mChat = new ArrayList<>();
    mAdapter = new msgAdapter(mChat,getContext());
    recyclerView.setAdapter(mAdapter);
    reference = FirebaseDatabase.getInstance().getReference("chats");
    reference.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
            mChat.clear();
            for (DataSnapshot snapshot: dataSnapshot.getChildren()){
                MessageChat chat = snapshot.getValue(MessageChat.class);
                mChat.add(chat);
                if (!chat.getSender().equals(Uid)){
                    Oid = chat.getSender();
                    seenMessage(Oid);
                }
            }
            mAdapter.notifyDataSetChanged();
        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {

        }
    });
}

// user status
private void status(String status){
    user = FirebaseAuth.getInstance().getCurrentUser();
    reference = FirebaseDatabase.getInstance().getReference("Users").child(user.getUid());

    HashMap<String, Object> hashMap = new HashMap<>();
    final Calendar c = Calendar.getInstance();
    int year = c.get(Calendar.YEAR);
    int month = c.get(Calendar.MONTH);
    month = month+1;
    int day = c.get(Calendar.DAY_OF_MONTH);
    int hour = c.get(Calendar.HOUR_OF_DAY);
    int sec = c.get(Calendar.MINUTE);
    String dt = day+" - "+month+" - "+year+", "+hour+":"+sec;
    hashMap.put("lastaccess", dt);
    hashMap.put("status", status);
    reference.updateChildren(hashMap);
}

@Override
public void onResume() {
    super.onResume();
    status("online");
    recyclerView.setAdapter(mAdapter);
}

@Override
public void onPause() {
    super.onPause();
    reference.removeEventListener(seenListener);
    status("offline");
}

}

I was also advised to add in here:

@Override
public void onResume() {
  super.onResume();
  status("online");
}

The following code:

recyclerView.setAdapter(mAdapter);

But with poor results (maybe you can suggest me if it is right to add this part here or not, thank you very much)

2) When I send a message, it often takes a long time to reach the database, let's say that Firebase is not really a realtime database. Here I have not encountered any type of error, it is simply very slow.

3) Later I also added the change of state, simply when the application is closed or in background ( onPause () ), the state is set to offline in the database, otherwise it is online. But often it doesn't work properly, probably application crashes or delays affect this.

I remain available for any other part of the code or for clarification. I thank you in advance for your help and I apologize for my bad English, I have been working on this application since last spring and I still have not been able to solve these problems.

1
First setAdapter as djnose said. Call readMessage() in onCreateView outside listener and remove first listener. Remove mChat.clear();. add new chat to mChats. Then call adapter's notifydatasetchanged() - Kasım Özdemir
You can check this out. - Alex Mamo
Thanks a lot for your comments and help, I modified the code in my post, and as a response to the first comment I reported the updates, but I still can't solve the problem. When I change activity and then go back to the chat activity, I get the blank screen without messages and if I try to go to another activity / function the app crashes and I receive the error message below. Also I don't understand why it is so slow to send messages to the firebase database. Thanks a lot for your help - mx88

1 Answers

1
votes

first thing you want to do is, remove the part in your onCreate.

reference = FirebaseDatabase.getInstance().getReference("chats");
reference.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
        readMessage();
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {

    }
});

Here you basically put a reference on the database (chats) and every time it changes, it will call your function readMessage, which will always override your messages and put another listener on the same spot. Make sure you understand, that addValueEventListener is triggered every time the tree/path (chats) in firebase is updated!

You could simply replace the code from above with:

readMessage();

that would already make sure it is listening to your db. I believe that firebase is not slow, but that the two listeners maybe overriding their result.

About the next part i am not 100%, but i believe you can remove

        mAdapter = new msgAdapter(mChat,getContext());
        recyclerView.setAdapter(mAdapter);

from onDataChange (in readMessage) and put it directly under that part where you create the ArrayList. Like this:

mChat = new ArrayList<>();
mAdapter = new msgAdapter(mChat,getContext());
recyclerView.setAdapter(mAdapter);
reference = FirebaseDatabase.getInstance().getReference("chats");

I hope i understood your problem and my answer will give you a bit more clarity :)

Greetings!