Only populate viewHolder with messages retrieved from the database?

I follow the Firebase code for Android.

Here's the Friendly Chat app project I'm trying to change: https://github.com/firebase/friendlychat-android/tree/master/android

I want the MessageViewHolder to be populated only after retrieving the message from the database. Unfortunately, now MessageViewHolder is also populated locally (after the user presses "SEND"). I do not want the latter to happen. This means that the owner of the view is updated twice (once locally and once after retrieving from the database), which is inefficient.

Side-node: In addition, the user might think that he is online when he presses "SEND" and sees that his MessageViewHolder is filled with his message. This is undesirable!

Please go to line 177 of MainActivity to see the populateViewHolder method.

I have been trying to avoid this problem for the past few months, but now I really need to solve the problem in order to continue working. What changes should I make to achieve what I want?

+7
java android firebase firebase-database firebaseui
source share
5 answers

I explained the script a bit. I presented two solutions in your case. The second solution is the simplest.

The reason it updates the local mMessageRecyclerView when you are offline is because Firebase has offline capabilities. Firebase starts / shuts down through a separate workflow. This workflow starts synchronization after re-entering the network β€” you may need to consider saving the contents of the workflows. After restarting the application, all local recording is disabled. Note that you installed mFirebaseAdapter in the main thread, as shown below:

 mFirebaseAdapter = new FirebaseRecyclerAdapter<FriendlyMessage, MessageViewHolder>(FriendlyMessage.class, R.layout.item_message, MessageViewHolder.class, mFirebaseDatabaseReference.child(MESSAGES_CHILD)) { /* more stuffs here */ } 

This means that any below 4 FirebaseRecyclerAdapter parameters change:

 FriendlyMessage.class, R.layout.item_message, MessageViewHolder.class, mFirebaseDatabaseReference.child(MESSAGES_CHILD) 

the observer inside the mFirebaseAdapter immediately discovers that it immediately changes and updates your mMessageRecyclerView RecyclerView. Therefore, you must ensure that you do not change any of these parameters until you successfully update Firebase.

Also note that mFirebaseDatabaseReference.child(MESSAGES_CHILD).push().setValue(friendlyMessage); , here the actual operation using the push network is happening in a separate workflow, as you know, the network operation cannot happen in the main topic, but the next line is mMessageEditText.setText(""); in the main topic. Thus, the workflow is working (successfully or unsuccessfully), the main thread has already updated your GUI.

Thus, the following solutions are possible:

1) Github's complete solution : https://github.com/uddhavgautam/UddhavFriendlyChat (you must ensure that you do not change above 4 parameters of your mFirebaseAdapter until you successfully update the Firebase update, so it's quite difficult. but it works just fine): here you create FriendlyMessage2 , which is exactly the same as FriendlyMessage (only FriendlyMessage is used instead of FriendlyMessage2 ) and use only FriendlyMessage inside onCompletionListener , as shown below:

In this solution, instead of updating the Firebase database with setValue() we use the REST record from the OkHttp client. This is because mFirebaseDatabaseReference1.child(MESSAGES_CHILD).push() triggers that update your RecyclerView locally, so using setValue() doesn't work here. onCompletionListener() comes later, so you can implement the logic inside onSuccess() . Using REST write, your RecyclerViewAdapter updates are based on firebase data. In addition, we need to serialize FriendlyMessage2 before we write using the Okhttp client through a separate workflow. So you should change your build.gradle as

update build.gradle file

  compile 'com.squareup.okhttp3:okhttp:3.5.0' compile 'com.google.code.gson:gson:2.8.0' 

Implementation of the setOnClickListener Method

  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 need to create this because your RecyclerView adapter depends on FriendlyMessage. Using FriendlyMessage, observers inside the RecyclerView adapter understand that, and therefore, refresh 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 all understand.

A simple solution from here:

2) A simple solution: you simply use OnLineTracker immediately after clicking the button, as shown below. I prefer the second method.

Decides: 1) MessageViewHolder does not populate offline. 2) MessageViewHolder is populated only when writing a Firebase database.

 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(); } } 
+1
source share

Use a transaction to send messages, not setValue. Thus, make sure you make a call online, and if (did not check this, but it should be), the listener will be a ValueEventListener, then this listener will not be called if it is disconnected.

If the data is added, you will see it, otherwise not (if not added locally). When creating a transaction, you have onComplete, where you can check if a message has been sent, and

Just use databaseReference.runTransaction(transactionHandler, false)

To make sure that the transaction is only performed when db is connected, you can check that firebase is connected before databaseReference.runTransaction

  override fun doTransaction(p0: MutableData?): Transaction.Result { p0?.value = objectToPut return Transaction.success(p0) } 

More about transactions: https://firebase.google.com/docs/database/android/read-and-write#save_data_as_transactions

How to check if it is:

 DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected"); connectedRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { boolean connected = snapshot.getValue(Boolean.class); if (connected) { System.out.println("connected"); } else { System.out.println("not connected"); } } @Override public void onCancelled(DatabaseError error) { System.err.println("Listener was cancelled"); } }); 
0
source share

Change the method as follows:

 protected void populateViewHolder(int populatorId, final MessageViewHolder viewHolder, FriendlyMessage friendlyMessage, int position); 

Now the user of this method must pass the populatorId and in the implementation you can handle it like this:

  @Override protected void populateViewHolder(int populatorId, final MessageViewHolder viewHolder, FriendlyMessage friendlyMessage, int position) { if( populatorId == FROM_DATABASE ){ //the implementation mProgressBar.setVisibility(ProgressBar.INVISIBLE); if (friendlyMessage.getText() != null) { viewHolder.messageTextView.setText(friendlyMessage.getText()); viewHolder.messageTextView.setVisibility(TextView.VISIBLE); viewHolder.messageImageView.setVisibility(ImageView.GONE); . . . }else{ //do nothing } 

UPDATE

Therefore, the user of this method can check the status of the network and then generate an identifier for it. then you can handle this Id in the implementation. To check the Internet before calling this method and example, if there is no use of the Internet populatorId = NO_INTERNET, you can check it during the implementation process and do something else.

0
source share

I have no more idea about Firebase . I think that if you check before you press the enter key available on the Internet, you can fix the problem. If the Internet is available, then run the firebase code, otherwise send a message or any other thing you want. Others believe that before upgrading the FirebaseRecyclerAdapter, check that the Internet is available or not. Finally, you need to check each of them wherever you call the firebase instance.

0
source share

This is an optional solution. Why not take advantage of the fact that the chat message adapter fires twice. (if you are not using a transaction) The first time you make a local call, you save the message in a local db with identifier. The adapter presents the chat message with the travel wheel as "sending a message ... When the second call comes up, the adapter pulls the chat message from db and sets it as" sent "and, therefore, updates the adapter with the sent message.

This way you can chat offline like on Google. Thus, if the application is restarted / crashed, all sent messages may have the option "send again". Also, the adapter can now easily display new messages and old (viewed) messages as usual.

0
source share

All Articles