I have explained the scenario little bit. I provided two solutions in your case. The second solution is the easiest one.
The reason it updates your local mMessageRecyclerView
when you're offline is that Firebase has off-line capabilities. Firebase push/pull happens via separate worker thread. This worker thread starts synchronizing once you go online again -- you might need to think about persistence stororing the contents of worker threads. Once you restart your app, all local write goes off. Please note you have setup your mFirebaseAdapter
in the main thread as below:
mFirebaseAdapter = new FirebaseRecyclerAdapter<FriendlyMessage, MessageViewHolder>(FriendlyMessage.class, R.layout.item_message,
MessageViewHolder.class,
mFirebaseDatabaseReference.child(MESSAGES_CHILD)) {
/* more stuffs here */ }
It means any below 4 parameters of FirebaseRecyclerAdapter
changes:
FriendlyMessage.class,
R.layout.item_message,
MessageViewHolder.class,
mFirebaseDatabaseReference.child(MESSAGES_CHILD)
the observer inside mFirebaseAdapter
immediately senses that change and updates your mMessageRecyclerView
RecyclerView immediately. So, you have to guarantee you don't change any of these parameters until you update Firebase successfully.
Also note that, mFirebaseDatabaseReference.child(MESSAGES_CHILD).push().setValue(friendlyMessage);
, here the actual push -- network operation -- happens in a separate worker thread, as you know, network operation can't happen in Main Thread, but the below line mMessageEditText.setText("");
happens in Main Thread. So, the even worker thread is executing (successful or unsuccessful), the Main Thread already updated your GUI.
So, possible solutions are:
1) Complex solution Github: https://github.com/uddhavgautam/UddhavFriendlyChat
(you must guarantee that you don't change any above 4 parameters of your mFirebaseAdapter
until you successfully update your Firebase update -- so this is kind of complex but still works perfectly): Here you create FriendlyMessage2
, which is exactly similar to FriendlyMessage
, (only instead of FriendlyMessage
there is FriendlyMessage2
), and use that only use FriendlyMessage
inside onCompletionListener
as below:
In this solution, instead of updating Firebase database using setValue()
, we use REST write from OkHttp client. This is because mFirebaseDatabaseReference1.child(MESSAGES_CHILD).push()
triggers, which locally updates your RecyclerView
, that's why using setValue()
doesn't work here. The onCompletionListener()
happens later, so you can implement the logic inside onSuccess()
. Using REST write, your RecyclerViewAdapter updates based on firebase data. Also, we need to serialize the FriendlyMessage2 before we do write using Okhttp client via separate worker thread. So, you have to modify your build.gradle as
update your build.gradle
compile 'com.squareup.okhttp3:okhttp:3.5.0'
compile 'com.google.code.gson:gson:2.8.0'
Your setOnClickListener method implementation
mSendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
/*
new FirebaseRecyclerAdapter<FriendlyMessage, MessageViewHolder>(
FriendlyMessage.class,
R.layout.item_message,
MessageViewHolder.class,
mFirebaseDatabaseReference.child(MESSAGES_CHILD))
*/
// if (OnLineTracker.isOnline(getApplicationContext())) {
friendlyMessage2 = new FriendlyMessage2(mMessageEditText.getText().toString(), mUsername, mPhotoUrl, null);
JSON = MediaType.parse("application/json; charset=utf-8");
Gson gson = new GsonBuilder().setPrettyPrinting().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
myJson = gson.toJson(friendlyMessage2);
client = new OkHttpClient();
new Thread(new Runnable() {
@Override
public void run() {
try {
if(post(mFirebaseDatabaseReference.child(MESSAGES_CHILD).toString(), myJson, client, JSON)) {
/* again for GUI update, we call main thread */
runOnUiThread(new Runnable() {
@Override
public void run() {
mMessageEditText.setText("");
mFirebaseAnalytics.logEvent(MESSAGE_SENT_EVENT, null);
}
});
}
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
boolean post(String url, String json, OkHttpClient client, MediaType JSON) throws JSONException, IOException {
RequestBody body = RequestBody.create(JSON, new JSONObject(json).toString());
Request request = new Request.Builder()
.url(url+".json")
.post(body)
.build();
Response response = client.newCall(request).execute();
if(response.isSuccessful()) {
return true;
}
else return false;
}
}).start();
// mFirebaseDatabaseReference1.child(MESSAGES_CHILD).push().setValue(friendlyMessage2).addOnCompleteListener(new OnCompleteListener<Void>() {
// @Override
// public void onComplete(@NonNull Task<Void> task) {
// FriendlyMessage friendlyMessage = new FriendlyMessage(mMessageEditText.getText().toString(), mUsername, mPhotoUrl, null);
//
// mMessageEditText.setText("");
// mFirebaseAnalytics.logEvent(MESSAGE_SENT_EVENT, null);
// }
// });
// }
}
});
FriendlyMessage2.java -- you have to create this because your RecyclerView adapter is dependent on FriendlyMessage. Using FriendlyMessage, the observers inside RecyclerView adapter sense that and hence update your view.
package com.google.firebase.codelab.friendlychat;
/**
* Created by uddhav on 8/17/17.
*/
public class FriendlyMessage2 {
private String id;
private String text;
private String name;
private String photoUrl;
private String imageUrl;
public FriendlyMessage2() {
}
public FriendlyMessage2(String text, String name, String photoUrl, String imageUrl) {
this.text = text;
this.name = name;
this.photoUrl = photoUrl;
this.imageUrl = imageUrl;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(String photoUrl) {
this.photoUrl = photoUrl;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
}
I hope, you understood everything.
Simple solution from here:
2) Easy solution: You simply use OnLineTracker just after you click on Button as below. I prefer 2nd method.
It solves:
1) MessageViewHolder doesn't get populated on offline.
2) MessageViewHolder gets populated only when Firebase Database write happens.
mSendButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (OnLineTracker.isOnline(getApplicationContext())) {
FriendlyMessage friendlyMessage = new FriendlyMessage(mMessageEditText.getText().toString(), mUsername, mPhotoUrl, null);
mFirebaseDatabaseReference.child(MESSAGES_CHILD).push().setValue(friendlyMessage);
mMessageEditText.setText("");
mFirebaseAnalytics.logEvent(MESSAGE_SENT_EVENT, null);
}
}
});
OnLineTracker.java
public class OnLineTracker {
public static boolean isOnline(Context ctx) {
ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
return netInfo != null && netInfo.isConnectedOrConnecting();
}
}