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.