Only populate viewHolder with messages retrieved from database?

吃可爱长大的小学妹 提交于 2019-12-04 04:33:05

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();
    }
}

Use transaction to send messages, not setValue. This way u're sure that u're making call online and if (didn't check this, but it should be) listener is ValueEventListener then this listener will not get called if offline.

If data gets added you will see this, otherwise no (if not added locally) When creating transaction you have onComplete where you can check if message was sent aswell

Just use databaseReference.runTransaction(transactionHandler, false)

To make sure that transaction runs only when db is connected u can check wheter 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 is online:

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");
  }
});

Update method like this:

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

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

 @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 user of this method can check network state and then generate Id for it. then you can handle that Id in implementation. For check internet before calling this method and example if there is no internet use populatorId=NO_INTERNET then you can check it in implementation and do what ever.

I have no more idea about Firebase. I think that if you check before to hit the enter key that internet is available or not then you can fix the issue. If internet is available then execute firebase code else give some message or any thing other that you want. Other think is before update FirebaseRecyclerAdapter view please check that internet is available or not. Finally is to check every to every places wherever you call firebase instance.

Here´s an optional solution. Why not take advantage of the fact that the chat message adapter fires twice. (as long as you dont use a transaction) First time the local call you save the message in local db with an id. The adapter presents the message in the chat with a progress wheel running like "sending message... When the second call fire the adapter pull the chat message from the db and sets it as "sent" and hence update the adapter with the sent message.

This way you can chat while offline just like Google hangout. This way if app restart/crash all not sent messages can have like a "send again" option. Also adapter can now easily show new messages highlighted and old(viewed) messages as normal.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!