2

I try to create a notification overlay inside my application that can show notifications about certain application wise important events. I decided to use a RecyclerView which will be drown directly on WindowManager. This works fine for showing initial items, however the the items don't get updated. Below is my implementation. So when I call start the not1 and not2 are shown, but when removeNotification function get called, the notification is actually being removed and a correct list is being submitted to the adapter, but the view on screen does not update. However if I add windowManager.updateViewLayout(recyclerView, layoutParams) after submitList inside removeNotification, everything seems to work as expected, but this time I am loosing RV animations..

As this is the first time I work with WindowManager directly, I am quite confused. Can someone help me to figure out what's going on and how can I achieve what I want to, if only that's possible.

class NotificationOverlay(private val context: Context) {

    private val windowManager: WindowManager =
        context.getSystemService(Context.WINDOW_SERVICE) as WindowManager

    private val layoutParams = WindowManager.LayoutParams().apply {
        gravity = android.view.Gravity.TOP or android.view.Gravity.CENTER_HORIZONTAL
        width = WindowManager.LayoutParams.MATCH_PARENT
        height = WindowManager.LayoutParams.WRAP_CONTENT
        format = PixelFormat.TRANSLUCENT
        dimAmount = 0.5f
        flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND
        type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
    }

    private val notifications = mutableListOf<NotificationItem>().also {
        it.addAll(listOf(
            NotificationItem(title = "not 1", message = "first notification"),
            NotificationItem(title = "not 2", message = "second notification")
        ))
    }
    private val notificationsAdapter = NotificationAdapter(::removeNotification)
    private val recyclerView = RecyclerView(context).apply {
        layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
        adapter = notificationsAdapter
    }


    private fun removeNotification(notification: NotificationItem){
        notifications.remove(notification)
        notificationsAdapter.submitList(notifications)
        if(notificationsAdapter.currentList.isEmpty()){
            windowManager.removeView(recyclerView)
        }
    }

    fun show(){
        windowManager.addView(recyclerView, layoutParams)

        notificationsAdapter.submitList(notifications)
    }
}

Edited

Well, I found out that the issue is not in WindowManager but rather in DiffUtils, but cannot understand what's wrong with it, as it is very simple one, and I implemented such DiffUtils a lot of times, anyways, I'll post the code here if you could have any idea on why this does not work:

    class NotificationAdapter(private val onCloseClicked: (NotificationItem) -> Unit):
        ListAdapter<NotificationItem, NotificationAdapter.NotificationViewHolder>(DiffCallback()) {
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationViewHolder {
            val binding = ItemNotificationOverlayBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return NotificationViewHolder(binding, onCloseClicked)
        }
    
    
        override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) {
            holder.bind(currentList[position])
        }
    
        class NotificationViewHolder(private val itemBinding: ItemNotificationOverlayBinding, private val onCloseClicked: (NotificationItem) -> Unit): RecyclerView.ViewHolder(itemBinding.root) {
            fun bind(item: NotificationItem) {
                itemBinding.title.text = item.title
                itemBinding.message.text = item.message
                itemBinding.close.setOnClickListener {
                    onCloseClicked.invoke(item)
                }
            }
        }
    
        class DiffCallback : DiffUtil.ItemCallback<NotificationItem>() {
    
            override fun areItemsTheSame(oldItem: NotificationItem, newItem: NotificationItem) =
                oldItem.id == newItem.id
    
            override fun areContentsTheSame(oldItem: NotificationItem, newItem: NotificationItem) =
                oldItem == newItem
        }
    }

What can every be wrong in such a simple construction? I am going crazy already and want to throw away this DiffUtils and implement the old school notifyItemRemoved

Edited 2

So the answer offered by @IamFr0ssT fixed the issue. I dig a bit deeper to see why this happens and the reason is in androidx.recyclerview.widget.AsyncListDiffer class in main submitList function. It is doing the following check there:

if (newList == mList) {
            // nothing to do (Note - still had to inc generation, since may have ongoing work)
            if (commitCallback != null) {
                commitCallback.run();
            }
            return;
        }

so my diff was never being even tried to be calculated as I was submitting the same reference of the list.

what the additional toList() function suggested by @IamFr0ssT did, is created a new instance of the list thus making the differ to calculate my diff. If you go deeper inside toList() function, it eventually creates a new instance of an ArrayList based on provided list ...return ArrayList(this)

So well, this issue had nothing to do with WindowManager just the DiffUtil

Andranik
  • 2,729
  • 1
  • 29
  • 45
  • 1
    If you manually call [notifyDataSetChanged](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.Adapter#notifyDataSetChanged()) after updating the list does it fix the behavior? – IamFr0ssT Jun 23 '22 at 12:30
  • @IamFr0ssT am really surprised, but it did.. now I try to understand what is wrong with my DiffCallback, as I implemented this way the diff callback many times before and it worked.. I'll update as soon as I find out what is wrong with my Diff – Andranik Jun 23 '22 at 15:33

2 Answers2

3

You are passing the MutableList notifications to the adapter, and the adapter is not making a copy of the list, it is just using the same list.

When you edit the list in your removeNotification callback, you are editing the list that the adapter is using.

Because of that, when the diff is being calculated, it is comparing the list that it thinks is currently displayed, but is not, to itself. Thus no diff and no notifyItemRemoved or other events.

What you can do to fix it, I think, is just call .toList() on the mutable list when you call submitList():

class NotificationOverlay(private val context: Context) {
    ...

    private fun removeNotification(notification: NotificationItem){
        notifications.remove(notification)
        notificationsAdapter.submitList(notifications.toList())
        if(notificationsAdapter.currentList.isEmpty()){
            windowManager.removeView(recyclerView)
        }
    }

    fun show(){
        windowManager.addView(recyclerView, layoutParams)

        notificationsAdapter.submitList(notifications.toList())
    }
}

Also, how do you get NotificationItem.id? It should be different for each entry.

IamFr0ssT
  • 729
  • 5
  • 11
1

Um.. I think you should try to manually notify your RecyclerView to redraw with notifyDataSetChanged()

If it doesn't work ListAdapter you are using as adapter does diff computation and dispatches the result to the RecyclerView. Maybe diff is not correct and the adapter does not see a difference between the old list and the new one - in this case it won't update UI. You may try to check diff behaviour and maybe comparing behavior of your data to change it.

Pavlo Ostasha
  • 14,527
  • 11
  • 35
  • 1
    Yes, but I assume NotificationAdapter extends [ListAdapter](https://developer.android.com/reference/androidx/recyclerview/widget/ListAdapter#submitList(java.util.List%3CT%3E)). In that case submitList should dispatch notifyItem events. – IamFr0ssT Jun 23 '22 at 12:39
  • 1
    @IamFr0ssT added expansion to the answer to address the comment – Pavlo Ostasha Jun 23 '22 at 12:42
  • @Pavlo Ostasha I am really surprised, but it did.. now I try to understand what is wrong with my DiffCallback, as I implemented this way the diff callback many times before and it worked.. I'll update as soon as I find out what is wrong with my Diff – Andranik Jun 23 '22 at 15:33
  • @PavloOstasha Hey can you please look this [issue](https://stackoverflow.com/q/72742819/11560810) – Kotlin Learner Jun 24 '22 at 11:04