只使用从数据库中检索的消息填充viewHolder?

时间:2017-06-30 22:39:16

标签: java android firebase firebase-realtime-database firebaseui

我关注Android Firebase Codelab。

这是我试图修改的友好聊天应用的项目:https://github.com/firebase/friendlychat-android/tree/master/android

我希望在从数据库中检索邮件后填充MessageViewHolder 。不幸的是,现在,MessageViewHolder也会在本地填充(在用户按下' SEND'之后)。我不希望后者发生。这意味着视图持有者会更新两次(一次在本地,一次从数据库中检索),这是低效的。

侧节点:此外,当用户按下“发送”按钮时,用户可能会认为他在线。并看到他的MessageViewHolder填充了他的消息。那是不受欢迎的!

请转到MainActivity第177行查看populateViewHolder方法。

我过去几个月一直试图回避这个问题,但现在我真的需要解决问题才能继续工作。 为实现我想要的目标,我必须做出哪些改变?

5 个答案:

答案 0 :(得分:1)

我已经解释过这个场景了。我提供了两种解决方案。第二种解决方案是最简单的解决方案。

当您离线时更新本地mMessageRecyclerView的原因是Firebase具有离线功能。 Firebase推/拉通过单独的工作线程发生。一旦你再次上线,这个工作线程就会开始同步 - 你可能需要考虑存储工作线程内容的持久性。重新启动应用后,所有本地写入都会消失。请注意,您已在主线程中设置了mFirebaseAdapter,如下所示:

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

这意味着FirebaseRecyclerAdapter的任何以下4个参数都会发生变化:

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

mFirebaseAdapter内的观察者立即感知到更改并立即更新您的mMessageRecyclerView RecyclerView。因此,在成功更新Firebase之前,您必须保证不会更改任何这些参数。

另请注意,mFirebaseDatabaseReference.child(MESSAGES_CHILD).push().setValue(friendlyMessage);,这里实际的推送 - 网络操作 - 发生在一个单独的工作线程中,如您所知,网络操作不会发生在主线程中,但下面的行mMessageEditText.setText("");发生在主线程中。因此,偶数工作线程正在执行(成功或不成功),主线程已经更新了您的GUI。

所以,可能的解决方案是:

1)复杂解决方案 Github:https://github.com/uddhavgautam/UddhavFriendlyChat (您必须保证在成功更新Firebase更新之前不要更改mFirebaseAdapter的任何上述4个参数 - 所以这有点复杂,但仍能正常运行):在这里创建{{1 }},与FriendlyMessage2完全相似,(仅FriendlyMessage而不是FriendlyMessage),并且只使用FriendlyMessage2内的FriendlyMessage,如下所示:

在此解决方案中,我们使用来自OkHttp客户端的REST写入,而不是使用onCompletionListener更新Firebase数据库。这是因为setValue()触发器会在本地更新您的mFirebaseDatabaseReference1.child(MESSAGES_CHILD).push(),这就是为什么使用RecyclerView无法在此处工作的原因。 setValue()稍后发生,因此您可以在onCompletionListener()中实现逻辑。使用REST写入,您的RecyclerViewAdapter会根据firebase数据进行更新。此外,我们需要在使用Okhttp客户端通过单独的工作线程编写之前序列化FriendlyMessage2。因此,您必须将build.gradle修改为

更新您的build.gradle

onSuccess()

您的setOnClickListener方法实现

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

FriendlyMessage2.java - 您必须创建此项,因为您的RecyclerView适配器依赖于FriendlyMessage。使用FriendlyMessage,RecyclerView适配器中的观察者会感知到并因此更新您的视图。

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

    //                }
                }
            });

我希望,你了解一切。

此处的简单解决方案:

2)简单的解决方案:您只需在点击按钮后立即使用OnLineTracker,如下所示。我更喜欢第二种方法。

它解决了: 1)MessageViewHolder不会在离线状态下填充。 2)只有在Firebase数据库写入发生时才会填充MessageViewHolder。

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

<强> OnLineTracker.java

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

答案 1 :(得分:0)

使用事务发送消息,而不是setValue。这样你确定你在网上打电话,如果(没有检查,但应该是)监听器是ValueEventListener,那么这个监听器在离线时不会被调用。

如果添加了数据,你会看到这个,否则没有(如果没有在本地添加)创建事务时你有onComplete,你可以检查是否发送了消息

只需使用databaseReference.runTransaction(transactionHandler, false)

即可

要确保只有在连接数据库时才运行事务,您可以在databaseReference.runTransaction

之前检查firebase是否已连接
    override fun doTransaction(p0: MutableData?): Transaction.Result {
                    p0?.value = objectToPut
                    return Transaction.success(p0)
    }

有关交易的更多信息:https://firebase.google.com/docs/database/android/read-and-write#save_data_as_transactions

如何检查是否在线:

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

答案 2 :(得分:0)

更新方法如下:

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

现在这个方法的用户应该传递populatorId,在实现中你可以像:

一样处理它
 @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

}

<强>更新

因此,此方法的用户可以检查网络状态,然后为其生成Id。那么你可以在实现中处理该Id。在调用此方法和示例之前检查互联网如果没有互联网使用populatorId = NO_INTERNET,那么你可以在实现中检查它并做任何事情。

答案 3 :(得分:0)

我对Firebase不再了解。我认为,如果您之前检查输入密钥是否可用互联网,那么您可以解决问题。如果互联网可用,那么执行firebase代码,否则给出一些消息或任何你想要的东西。其他想法是在更新FirebaseRecyclerAdapter之前查看请检查互联网是否可用。最后是每次调用firebase实例的每个地方都要检查。

答案 4 :(得分:0)

这是一个可选的解决方案。为什么不利用聊天消息适配器触发两次的事实。 (只要你不使用交易) 第一次本地调用时,使用id将消息保存在本地数据库中。适配器在聊天中显示消息,其进度轮运行如“发送消息...当第二次调用时,适配器从数据库中拉出聊天消息并将其设置为”已发送“,因此使用已发送的消息更新适配器。

通过这种方式,您可以在离线时进行聊天,就像Google视频群聊一样。这样,如果app重启/崩溃,所有未发送的消息都可以像“再次发送”选项一样。 此外,适配器现在可以正常显示突出显示的新消息和旧(已查看)消息。