【问题标题】:Recyclerview Adapter's notifyDataSetChanged not updating RecyclerViewRecyclerview 适配器的 notifyDataSetChanged 不更新 RecyclerView
【发布时间】:2018-11-23 08:40:07
【问题描述】:

我有一个 RecyclerView 用于带有 firebase 实时数据库和本地 SQLite 数据库的聊天应用程序。我正在将消息保存到本地数据库 (SQLite) 中,然后调用 adapter.notifyDataSetChanged()

如果消息已经在数据库中(消息唯一 id),那么 SQLite 将在数据库 insertOrThrow 方法上返回 0。我正在检查这样的可用性。

if (id == 0) {
    Log.d(TAG,"Database Already Has Value Of This Random Id ");
    adapter.notifyDataSetChanged();
} else {
    Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, UserPhone_Intent, UserImage_Intent, Chat_FROM, null,null,id);
    message.add(chat_wrapper);
    adapter.notifyDataSetChanged();
}

但是,即使调用了else 语句,我的RecyclerView 屏幕也没有更新,但是在后台,如果我键入或接收到任何消息,它会保存到本地数据库中,但不会显示在屏幕上。

Chat RecyclerView 适用于以下情况

  1. 当我从最近的应用中清除应用时
  2. 停止从本地数据库获取数据
  3. 首次启动聊天屏幕

当我从通知直接进入聊天屏幕时,我遇到了这个问题。

这就是我正常加载聊天片段的方式。

getFragmentManager().beginTransaction().add(R.id.Navigation_Drawer, chatFragment).commit();

使用AsynctaskNotification加载Fragment

((Navigation_Drawer)context).getFragmentManager().beginTransaction().replace(R.id.Navigation_Drawer, chatFragment).commit();

这就是我从通知中启动 ChatFragment 的方式。

public class FireBase_Messaging_Service extends FirebaseMessagingService {

    public static final String TAG="###FireBase MSG###";
    public static final int NOTIFICATION=5;
    String UserName;
    String ID;
    String Msg;

    Map<String,String> data;
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);
        Log.d(TAG,"From "+remoteMessage.getFrom());
        if (remoteMessage.getData().size()>0){
            data = remoteMessage.getData();
            Log.d(TAG,"Message Data "+remoteMessage.getData());
            data = remoteMessage.getData();

            UserName = data.get("name");
            ID = data.get("ID");
            Msg = data.get("Message");

            showNotification(Msg,ID,UserName);
        }

        if (remoteMessage.getNotification()!=null){
            Log.d(TAG,"Message Notification Body "+remoteMessage.getNotification().getBody());
           // Toast.makeText(this, "Notification "+remoteMessage.getNotification().getBody(), Toast.LENGTH_LONG).show();
        }
    }

    private void showNotification(String Message,String ID,String UserName) {
        Log.d(TAG,"Show Notification "+Message+" "+ID);
        Intent intent=new Intent(this, Navigation_Drawer.class);
        intent.putExtra("Type","Text");
        //intent.putExtra("Type",MsgType);
        intent.putExtra("ID",ID);
        intent.putExtra("uname",UserName);
        intent.putExtra("Message",Msg);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent pendingIntent=PendingIntent.getActivity(this,NOTIFICATION,intent,PendingIntent.FLAG_UPDATE_CURRENT);
        int color = getResources().getColor(R.color.black);
        String ChannelID = "Message";
        notificationChannel(ChannelID,"Chat");
        NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(),ChannelID)
                .setSmallIcon(R.drawable.default_x)
                .setColor(color)
                .setContentTitle(UserName)
                .setContentText(Message)
                .setChannelId(ChannelID)
                .setTicker("My App")
                .setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND | Notification.FLAG_SHOW_LIGHTS)
                .setLights(0xff00ff00, 1000, 500) // To change Light Colors
                .setStyle(new NotificationCompat.BigTextStyle().bigText(Message))//For Expandable View
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true);

        NotificationManagerCompat managerCompat = NotificationManagerCompat.from(this);
        managerCompat.notify(NOTIFICATION,builder.build());
    }

    @Override
    public void onDeletedMessages() {
        super.onDeletedMessages();
    }

    private void notificationChannel (String ChannelID, String channelName) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            NotificationChannel  channel = new NotificationChannel(ChannelID,channelName, NotificationManager.IMPORTANCE_DEFAULT);
            channel.setLightColor(Color.GREEN);
            NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);
        }
    }
}

我还注意到,在将数据添加到列表 message.add(chat_wrapper) 后,它首先显示大小增加,但是当 while 循环结束时,它显示 ArrayList 的最后一个大小。

这是ChatFragment 类。

 public class Chat_Screen_Fragment extends Fragment implements View.OnClickListener, ChildEventListener{

    public static final String TAG = "###CHAT SCREEN###";
    List<Chat_Wrapper> message = new ArrayList<>();

    Chat_Adapter adapter;
    RecyclerView recyclerView;
    LinearLayoutManager layoutManager;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View v=inflater.inflate(R.layout.chat_screen_main_fragment,container,false);
        setRetainInstance(true);
        // GET INTENT VALUES FROM USER PROFILE CLASS

        UserName_Intent = getArguments().getString("Get_Name");
        UserImage_Intent = getArguments().getString("Get_Image");
        UserPhone_Intent = getArguments().getString("Get_Phone");
        UserID_Intent = getArguments().getString("Get_ID");
        FirebaseToken_Intent = getArguments().getString("Get_Token"); //Firebase Token of other person
        Room_Name_Intent = getArguments().getString("Get_Other"); // Room Name of chat
        UserLastSeen_Intent=getArguments().getString("LastSeen");
        //Sender_FCMToken = Session.getFirebaseID();
        // RECYCLER VIEW
        recyclerView = v.findViewById(R.id.Chat_Screen_Message_List);
        layoutManager = new LinearLayoutManager(getActivity());
        layoutManager.setStackFromEnd(true);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(layoutManager);
        databaseReference = FirebaseDatabase.getInstance().getReference().child(Room_Name_Intent);
        databaseReference.addChildEventListener(this);

        adapter = new Chat_Adapter(getActivity(), message);
        recyclerView.setAdapter(adapter);
        // FETCH OLD MESSAGE FROM DATABASE
        chatDatabase();
        return v;
    }
    // FIREBASE REAL TIME DATABASE WHICH FETCH ALL MESSAGES (SYNC) FROM ONLINE DATABASE
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String s) {
        append_chat_conversation(dataSnapshot);
    }

    @Override
    public void onChildChanged(DataSnapshot dataSnapshot, String s) {
        append_chat_conversation(dataSnapshot);
    }

    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {

    }

    @Override
    public void onChildMoved(DataSnapshot dataSnapshot, String s) {

    }

    @Override
    public void onCancelled(DatabaseError databaseError) {

    }

    private synchronized void append_chat_conversation(DataSnapshot dataSnapshot) {
        iterator = dataSnapshot.getChildren().iterator();

        while (iterator.hasNext()) {

            // GETTING DATA FROM FIREBASE DATABASE 

            Chat_Msg = (String) ((DataSnapshot) iterator.next()).getValue();
            Chat_FROM = (String) ((DataSnapshot) iterator.next()).getValue();
            Chat_FROM_ID = (String) ((DataSnapshot) iterator.next()).getValue();
            Chat_TO = (String) ((DataSnapshot) iterator.next()).getValue();
            Chat_TimeStamp = (String) ((DataSnapshot) iterator.next()).getValue();
            Chat_Type= (String) ((DataSnapshot) iterator.next()).getValue();
            Random_ID=(String) ((DataSnapshot) iterator.next()).getValue();
            Chat_FCM_FROM= (String) ((DataSnapshot) iterator.next()).getValue();
            Chat_FCM_TO= (String) ((DataSnapshot) iterator.next()).getValue();

            Log.d(TAG, "Chat Items " + Chat_Msg + " " + Random_ID);

            Chat_Database tempChatDatabase = new Chat_Database(getActivity());
            boolean hasValue = tempChatDatabase.CheckValueExist(Random_ID);
            Log.d(TAG,"DATABASE ALREADY HAS VALUE OF TIMESTAMP= "+hasValue);

            if (!hasValue) {
                Log.d(TAG,"DATABASE DON'T HAVE SAME ENTRY FOR TIME STAMP. ENTERED INTO HAS VALUE");
                Log.d(TAG,"Chat Message "+Chat_Msg);

           if (Chat_Type.equals("Typed_Message")) {
                Log.d(TAG, "VIEW TYPE IS Message " + Chat_Msg);
                long id = chat_database.Insert_Chat(Session.getUserID(),Room_Name_Intent, UserID_Intent, "Text", Chat_Msg, Chat_FROM, Chat_TO, Chat_TimeStamp, Chat_FCM_FROM, Chat_FCM_TO, Session.getPhoneNO(), UserPhone_Intent,Random_ID,UserImage_Intent,UserLastSeen_Intent,Chat_FROM_ID);

                //Adding Chat Data Into Database
                Log.d(TAG,"Database Entry ID "+id);

                if (id == 0) {
                    Log.d(TAG,"Database Already Has Value Of This Random Id ");
                    adapter.notifyDataSetChanged();
                    continue;

                } else {
                    Log.d(TAG,"Database Don't Has Value Of This Random Id ");
                    Log.d(TAG,"Message Size "+message.size());
                    Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, UserPhone_Intent, UserImage_Intent, Chat_FROM, null,null,id);
                    message.add(chat_wrapper);
                    Log.d(TAG,"Message Size "+message.size());
                    adapter.notifyDataSetChanged();
                    Log.d(TAG,"Adapter Notified Data Set "+adapter.getItemCount());
                    recyclerView.post(new Runnable() {
                        @Override
                        public void run() {
                            Log.d(TAG, "Moving to Bottom");
                            recyclerView.smoothScrollToPosition(adapter.getItemCount());
                        }
                    });
                }
        }

        Log.d(TAG, "MESSAGE ARRAY SIZE " + message.size());
        chat_database.isDatabaseClose();
}

    private void chatDatabase(){
        //Database Init and Filling Adapter
        Log.d(TAG,"Chat Database Function");
        chat_database=new Chat_Database(getActivity());
        chatCursor=chat_database.getUserChat(UserID_Intent);
        boolean checkDB_Exist=functions.DatabaseExist(getActivity(),"CHAT_DATABASE.DB");
        boolean chatItemsCounts=chatCursor.getCount()>0;
        chatCursor.moveToFirst();
        Log.d(TAG,"Value At Chat Database "+ checkDB_Exist+" "+chatItemsCounts);
        if (checkDB_Exist && chatCursor.getCount()>0 && chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID")).equals(UserID_Intent)){

            Log.d(TAG,"Database Exist Chat Database");
            message.clear();
            chatCursor.moveToFirst();
            do {
                database_rowID=chatCursor.getInt(chatCursor.getColumnIndex("ID"));
                database_userID=chatCursor.getString(chatCursor.getColumnIndex("USER_ID"));
                database_RoomName =chatCursor.getString(chatCursor.getColumnIndex("ROOM_NAME"));
                database_ReceiverID=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID"));
                database_MessageType=chatCursor.getString(chatCursor.getColumnIndex("MESSAGE_TYPE"));
                database_Message=chatCursor.getString(chatCursor.getColumnIndex("USER_MESSAGE"));
                database_MsgFrom=chatCursor.getString(chatCursor.getColumnIndex("SENDER_NAME"));
                database_MsgTo=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_NAME"));
                database_TimeStamp=chatCursor.getString(chatCursor.getColumnIndex("TIME_STAMP"));
                database_FCMfrom=chatCursor.getString(chatCursor.getColumnIndex("SENDER_TOKEN"));
                database_FCMto=chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_TOKEN"));
                database_LocalPath=chatCursor.getString(chatCursor.getColumnIndex("DOWNLOADED_AT"));
                database_PhoneFrom=chatCursor.getString(chatCursor.getColumnIndex("MY_PHONE"));
                database_PhoneTo=chatCursor.getString(chatCursor.getColumnIndex("OTHER_PHONE"));

                Log.d(TAG,"Value Of Database Message String = "+database_Message);
                Log.d(TAG,"Row ID of Database "+database_rowID);
                // Check Message Type


                    Log.d(TAG,"Message Type Is Text");
                    Chat_Wrapper text = new Chat_Wrapper(database_Message, null, null, null, null, null, null, database_TimeStamp, database_PhoneTo, UserImage_Intent, database_MsgFrom,null,null,database_rowID);
                    message.add(text);



            }
            while(chatCursor.moveToNext());

            Room_Name_Intent = database_RoomName;
            layoutManager = new LinearLayoutManager(getActivity());
            recyclerView.setLayoutManager(layoutManager);
            adapter.notifyDataSetChanged();
            chatCursor.close();
            boolean value = chat_database.isDatabaseClose();
            recyclerView.post(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG, "Moving to Bottom");
                    recyclerView.smoothScrollToPosition(message.size()-1);

                }
            });
            Log.d(TAG,"Value Of Database Close or Not "+value);

        }
    }
}

【问题讨论】:

  • 我没有看到您的适配器实现,但可以安全地假设它持有对消息 List 的引用而不是将值复制到新集合中吗?
  • @eimmer 见onCreateView 最后一行有适配器实现。
  • 我看到它在哪里初始化,没有实现。 Chat_Adapter 是您自己定义的类,还是由 Firebase 提供的?
  • @eimmer 它的自定义适配器不是 firebase one

标签: java android sql arrays database


【解决方案1】:

我通过深入研究 android 生命周期解决了这个问题。正如我上面提到的,只有当我从通知中输入 ChatFragment 时,我才会遇到这个问题。我只是活动状态问题。代码正在工作。我在做什么我总是在通知点击时重新创建活动(在 Firebase 代码中)。

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);

在挖掘大量代码并更改Intent 中的多个标志之后。只有android:launchMode="singleTask" 解决了我的问题。仅在不存在时才创建活动。现在它在点击通知时不会调用活动onDestroy 方法。它从onStart 开始活动,避免重新创建活动。

我在 AndroidManifest 的 NavigationDrawer 活动标签中添加了这个标志。

 <activity
            android:name=".Navigation_Drawer"
            android:launchMode="singleTask"
            android:theme="@style/AppTheme"/>

这类问题确实让开发人员头疼,但我不知道通过这行代码我可以解决这个问题。谢谢大家的帮助。

【讨论】:

    【解决方案2】:

    我认为您需要在此处实现LoaderCallbacks 并听取数据库中所做的更改以相应地更新RecyclerView。我在这里创建了一个repository in Github,它显示了数据库的读取、写入和更新,并在数据库中进行更改以在相应的RecyclerView 中产生适当的效果,并将内容观察者注册到数据库。

    整个想法是让一个观察者附加到您的数据库表中,它会通知您表中的任何更改。当检测到更改时,将自动生成数据库读取查询,并且在再次从表中重新加载数据时,将触发 RecyclerView 内部 onLoadFinished 函数的更新。

    我在我的 Github 项目中有一个演示实现,它显示了要在 RecyclerView 中显示的用户列表,并自动更新 RecyclerView 而无需调用 notifyDataSetChanged。请再次检查控制要在RecyclerView 中显示的数据的适配器。这是您可能在您的情况下避免任何复杂性的最简单的实现。

    希望对您有所帮助。

    更新

    浏览完代码后,我提出了一些你应该做的修复。很难理解为什么它没有像您预期的那样工作,因为给出的代码格式和完整都不是很好。但是,我可以在这里向您建议一些可能会有所帮助的关键修复。

    append_chat_conversation 函数中,您不必每次找到新的聊天消息并将其添加到message 时都调用notifyDataSetChanged。我想建议,不要将聊天消息添加到您的message 列表中。您可能会摆脱以下 if-else 块。只需更新聊天数据库并将新的聊天消息插入其中即可。所以我建议你删除以下部分。

    if (id == 0) {
        Log.d(TAG,"Database Already Has Value Of This Random Id ");
        adapter.notifyDataSetChanged();
        continue;
    
    } else {
        Log.d(TAG,"Database Don't Has Value Of This Random Id ");
        Log.d(TAG,"Message Size "+message.size());
        Chat_Wrapper chat_wrapper = new Chat_Wrapper(Chat_Msg, null, null, null, null, null, null, Chat_TimeStamp, UserPhone_Intent, UserImage_Intent, Chat_FROM, null,null,id);
        message.add(chat_wrapper);
        Log.d(TAG,"Message Size "+message.size());
        adapter.notifyDataSetChanged();
        Log.d(TAG,"Adapter Notified Data Set "+adapter.getItemCount());
        recyclerView.post(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "Moving to Bottom");
                recyclerView.smoothScrollToPosition(adapter.getItemCount());
            }
        });
    } 
    

    在此行之前调用chatDatabase 函数以从数据库加载消息。

    // FETCH MESSAGE FROM DATABASE
    chatDatabase();
    
    Log.d(TAG, "MESSAGE ARRAY SIZE " + message.size());
    chat_database.isDatabaseClose();
    

    现在修复您的 chatDatabase 函数。您正在创建一个新的 LinearLayoutManager 并在每次调用 chatDatabase 函数时将其分配给您的 RecyclerView。我建议也将其删除。

    修改您的chatDatabase 函数,如下所示。

    private void chatDatabase() {
        //Database Init and Filling Adapter
        Log.d(TAG, "Chat Database Function");
        chat_database = new Chat_Database(getActivity());
        chatCursor = chat_database.getUserChat(UserID_Intent);
        boolean checkDB_Exist = functions.DatabaseExist(getActivity(), "CHAT_DATABASE.DB");
        boolean chatItemsCounts = chatCursor.getCount() > 0;
        chatCursor.moveToFirst();
        Log.d(TAG, "Value At Chat Database " + checkDB_Exist + " " + chatItemsCounts);
        if (checkDB_Exist && chatCursor.getCount() > 0 && chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID")).equals(UserID_Intent)) {
    
            Log.d(TAG, "Database Exist Chat Database");
            message.clear();
            chatCursor.moveToFirst();
            do {
                database_rowID = chatCursor.getInt(chatCursor.getColumnIndex("ID"));
                database_userID = chatCursor.getString(chatCursor.getColumnIndex("USER_ID"));
                database_RoomName = chatCursor.getString(chatCursor.getColumnIndex("ROOM_NAME"));
                database_ReceiverID = chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID"));
                database_MessageType = chatCursor.getString(chatCursor.getColumnIndex("MESSAGE_TYPE"));
                database_Message = chatCursor.getString(chatCursor.getColumnIndex("USER_MESSAGE"));
                database_MsgFrom = chatCursor.getString(chatCursor.getColumnIndex("SENDER_NAME"));
                database_MsgTo = chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_NAME"));
                database_TimeStamp = chatCursor.getString(chatCursor.getColumnIndex("TIME_STAMP"));
                database_FCMfrom = chatCursor.getString(chatCursor.getColumnIndex("SENDER_TOKEN"));
                database_FCMto = chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_TOKEN"));
                database_LocalPath = chatCursor.getString(chatCursor.getColumnIndex("DOWNLOADED_AT"));
                database_PhoneFrom = chatCursor.getString(chatCursor.getColumnIndex("MY_PHONE"));
                database_PhoneTo = chatCursor.getString(chatCursor.getColumnIndex("OTHER_PHONE"));
    
                Log.d(TAG, "Value Of Database Message String = " + database_Message);
                Log.d(TAG, "Row ID of Database " + database_rowID);
                // Check Message Type
    
    
                Log.d(TAG, "Message Type Is Text");
                Chat_Wrapper text = new Chat_Wrapper(database_Message, null, null, null, null, null, null, database_TimeStamp, database_PhoneTo, UserImage_Intent, database_MsgFrom, null, null, database_rowID);
                message.add(text);
    
            } while (chatCursor.moveToNext());
    
            Room_Name_Intent = database_RoomName;
    
            adapter.setChatMessages(message); // Add a public function in your adapter to set the chat messages 
            adapter.notifyDataSetChanged();
    
            chatCursor.close();
            boolean value = chat_database.isDatabaseClose();
            recyclerView.post(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG, "Moving to Bottom");
                    recyclerView.smoothScrollToPosition(message.size() - 1);
    
                }
            });
    
            Log.d(TAG, "Value Of Database Close or Not " + value);
        }
    }
    

    适配器中的setChatMessages 函数看起来像这样。

    public void setChatMessages(ArrayList<Message> messageList) {
        this.message = messageList;
    }
    

    【讨论】:

    • 你能指导一下吗,因为它只有在我们从通知进入应用程序时才会发生,否则它工作正常。我在上面提到了它工作正常的情况。你能调整我们的代码让它工作吗?
    • 我正在检查它。带着结果回来。如果不工作,我会尝试为您提供格式正确的完整代码,以便您轻松获得它。
    • 我几乎没有疑问。每当收到新消息时获取完整的消息列表是否很好。定期调用数据库作为设备的性能是否良好。我正在处理您的代码,我会尽快更新您。非常感谢您的帮助。
    • 使用Caused by: android.database.CursorIndexOutOfBoundsException: Index 2 requested, with a size of 2 at line if (checkDB_Exist &amp;&amp; chatCursor.getCount() &gt; 0 &amp;&amp; chatCursor.getString(chatCursor.getColumnIndex("RECEIVER_USER_ID")).equals(UserID_Intent)) { 继续崩溃 仔细检查光标移动到第一个但仍然出现此问题。如果您说我会为您提供完整的代码,以便您轻松解决问题。谢谢和问候。
    • 我认为这个错误完全是指SQLite中保存的数据的问题。如果您认为这没有隐私问题,您可以在 Github 中分享代码。但是,SO 问题应该足够清晰和简短,以便清楚地描述问题。
    猜你喜欢
    • 1970-01-01
    • 2015-02-27
    • 1970-01-01
    • 1970-01-01
    • 2017-03-19
    • 2014-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多