Home > other >  Recycler View doesn't update list after notifyDataSetChanged()
Recycler View doesn't update list after notifyDataSetChanged()

Time:04-24

to describe the problem simply, i have main activity where it has viewPager2 which displays 2 fragments. The main activity also obtains a toolbar with search MenuOptions. i am trying to search artist therefore it will automatically update the recycler view in the first fragment (videoCallHomefragment). i don't know where is the problem as this is my 3rd day in debugging this matter and i still ended up nowhere.

my assumptions

  1. there must be conflict because i am using viewpager2 and trying to update recycler view in a fragment from an activity
  2. dependencies versioning conflict that results in "notifyDataSetChanged()" not working, on the other hand, i tried an alternative using DiffUtil and still not working

p.s. the app successfully retrieves artists and displays them in recycler view which indicated that the function "setData" in the adapter is working, but when searching for artist, the "onBindViewHolder" and "onCreateViewHolder" doesn't get initiated or called when calling notifyDataSetChanged()

See Below code snippets

MainActivity

@AndroidEntryPoint
class MainActivity : AppCompatActivity(), ArtistsAdapter.OnArtistListener {

    private lateinit var binding: ActivityMainBinding

    private val mAdapter by lazy { ArtistsAdapter(this) }

    private val mainViewModel: MainViewModel by viewModels()
    private lateinit var viewPager2:ViewPager2
    private lateinit var fragement: Fragment

    private lateinit var artists: List<Artist>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)


        fragement = supportFragmentManager.findFragmentById(R.id.videoCallHomeFragment)!!
        setSupportActionBar(findViewById(R.id.toolbar))
        val tabLayout = findViewById<TabLayout>(R.id.tab_layout)
        viewPager2 =  findViewById(R.id.view_pager_2)

        val adapter  = ViewPagerAdapter(supportFragmentManager, lifecycle)
        viewPager2.adapter = adapter
        TabLayoutMediator(tabLayout, viewPager2){tab, postion ->
            when(postion) {
                0 -> {
                    tab.text="Artists"
                }
                1 -> {
                    tab.text = "Voice Calls"
                }
            }
        }.attach()

        viewPager2.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback(), SearchView.OnQueryTextListener {
            @SuppressLint("ResourceType")
            override fun onPageSelected(position: Int) {
                if(position == 0)
                {

                    val toolbar = findViewById<Toolbar>(R.id.mainToolbar)
                    toolbar.inflateMenu(R.menu.search_menu)
                    val search = toolbar.menu.findItem(R.id.menu_search)
                    val searchView = search?.actionView as? SearchView
                    searchView?.isSubmitButtonEnabled = true
                    searchView?.setOnQueryTextListener(this)

                } else {
                    val toolbar = findViewById<Toolbar>(R.id.mainToolbar)
                    toolbar.menu.clear()
                }
            }

            @SuppressLint("NotifyDataSetChanged")
            override fun onQueryTextSubmit(query: String?): Boolean {
                if(query != null){
//                    recyclerView.adapter = mAdapter
//                    recyclerView.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false)
                    searchArtist(query)
                    mAdapter.notifyDataSetChanged()
                }
                return true
            }

            @SuppressLint("NotifyDataSetChanged")
            override fun onQueryTextChange(newText: String?): Boolean {
                if(newText != null){
//                    recyclerView.adapter = mAdapter
//                    recyclerView.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false)
                    searchArtist(newText)
                    mAdapter.notifyDataSetChanged()
                }
                return true
            }
        })

        requestPermissions()

        val toolbar = findViewById<Toolbar>(R.id.mainToolbar)
        toolbar.setBackgroundColor(Color.rgb(32, 4, 209))
        toolbar.setTitleTextColor(Color.WHITE)
        toolbar.setTitle(R.string.app_name)
    }

    private fun requestPermissions()
    {
        if (PermissionRequestUtil.hasCameraPermissions(this)) {
            return
        } else {
            EasyPermissions.requestPermissions(
                this,
                "Please accept camera permissions to use this app.",
                0,
                Manifest.permission.CAMERA
            )
        }
    }

    @SuppressLint("NotifyDataSetChanged")
    private fun searchArtist(query: String)
    {
        val searchQuery = "%$query%"
        lifecycle.coroutineScope.launch {
            mainViewModel.searchArtist(searchQuery).collect { it ->
                    mAdapter.setData(it)
                mAdapter.notifyDataSetChanged()
                Log.i(TAG, "new artist List set!!")
            }
        }
    }



    override fun onArtistClick(artist: Artist, position: Int) {
        TODO("Not yet implemented")
    }


}

VideoCallHomeFragment

package com.example.awfc.ui

import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.*
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.coroutineScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.awfc.R
import com.example.awfc.adapters.ArtistsAdapter
import com.example.awfc.data.Artist
import com.example.awfc.viewmodels.MainViewModel
import com.todkars.shimmer.ShimmerRecyclerView
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch


@AndroidEntryPoint
class VideoCallHomeFragment : Fragment() , ArtistsAdapter.OnArtistListener {
    private lateinit var mainViewModel: MainViewModel
    private val mAdapter by lazy { ArtistsAdapter(this)}
    private lateinit var mView: View
    private lateinit var artists: List<Artist>


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        mView = inflater.inflate(R.layout.fragment_video_call_home, container, false)
        setHasOptionsMenu(true)

        //val shimmer = mView.findViewById<ShimmerRecyclerView>(R.id.recycler_view)
        //shimmer.showShimmer()
        setupRecyclerView()
        mainViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
        lifecycle.coroutineScope.launch {
            mainViewModel.getArtists().collect {
                artists = it
                mAdapter.setData(it)
                //shimmer.hideShimmer()
            }
        }

        return mView
    }




    @SuppressLint("CutPasteId")
    fun setupRecyclerView()
    {
        mView.findViewById<RecyclerView>(R.id.recycler_view).adapter = mAdapter
        mView.findViewById<RecyclerView>(R.id.recycler_view).layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)

    }

    override fun onArtistClick(artist: Artist, position: Int) {
        artists[position]
        val intent = Intent(this.context, ArtistDetailsActivity::class.java)
        intent.putExtra("artistName", artist.name)
        intent.putExtra("arabicName", artist.name_arabic)
        intent.putExtra("arabicDesc", artist.description_arabic)
        intent.putExtra("artistDesc", artist.description)
        intent.putExtra("artistImage", artist.image)
        intent.putExtra("artistVideo1", artist.videoUrl1)
        intent.putExtra("artistVideo2", artist.videoUrl2)
        intent.putExtra("artistVideo3", artist.videoUrl3)
        startActivity(intent)
    }

}

Artist Adapter

package com.example.awfc.adapters

import android.annotation.SuppressLint
import android.content.ContentValues.TAG
import android.content.Intent
import android.service.autofill.OnClickAction
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.example.awfc.data.Artist
import com.example.awfc.databinding.ArtistRowLayoutBinding
import com.example.awfc.utils.ArtistsDiffUtil

class ArtistsAdapter(var artistListener: OnArtistListener) : RecyclerView.Adapter<ArtistsAdapter.MyViewHolder>() {

    private var artists = emptyList<Artist>()

    class MyViewHolder(private val binding: ArtistRowLayoutBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun init(artist: Artist, action: OnArtistListener)
        {
            itemView.setOnClickListener {
                action.onArtistClick(artist, adapterPosition)
            }
        }

        fun bind(modelClass: Artist) {
            binding.result = modelClass
            binding.executePendingBindings()
        }


        companion object {
            fun from(parent: ViewGroup): MyViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = ArtistRowLayoutBinding.inflate(layoutInflater, parent, false)
                return MyViewHolder(binding)
            }
        }

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        Log.i(TAG, "OnCreateViewHolder initiated")
        return MyViewHolder(
            ArtistRowLayoutBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val currentResult = artists[position]
        holder.bind(currentResult)
        holder.init(currentResult, artistListener)
        Log.i(TAG, "OnBindViewHOlder initiated")
    }

    override fun getItemCount(): Int {
        return artists.size
    }

    @SuppressLint("NotifyDataSetChanged")
    fun setData(newData: List<Artist>) {
        val artistsDiffUtil = ArtistsDiffUtil(artists, newData)
        val diffUtilResult = DiffUtil.calculateDiff(artistsDiffUtil)
        artists = emptyList()
        artists = newData
        diffUtilResult.dispatchUpdatesTo(this)
        this.notifyDataSetChanged()
        //this.notifyDataSetChanged()
    }
    interface OnArtistListener {
        fun onArtistClick(artist:Artist, position: Int)
    }
}

artistDiffUtil

package com.example.awfc.utils

import androidx.recyclerview.widget.DiffUtil

class ArtistsDiffUtil<T>(
    private val oldList: List<T>,
    private val newList: List<T>
): DiffUtil.Callback() {
    override fun getOldListSize(): Int {
        return oldList.size
    }

    override fun getNewListSize(): Int {
        return newList.size
    }

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition] === newList[newItemPosition]
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition] == newList[newItemPosition]
    }

}

gradleFile

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
    id 'kotlin-parcelize'
    id 'androidx.navigation.safeargs'
}

android {
    compileSdk 31

    buildFeatures {
        viewBinding true
        dataBinding true
    }

    defaultConfig {
        applicationId "com.example.awfc"
        minSdk 21
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        javaCompileOptions {
            annotationProcessorOptions {
                arguments  = ["room.schemaLocation":
                                      "$projectDir/schemas".toString()]
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    def room_version = "2.4.2"

    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    implementation "androidx.room:room-ktx:$room_version"

    implementation "com.google.dagger:hilt-android:2.28.3-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28.3-alpha"

    implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02"
    kapt "androidx.hilt:hilt-compiler:1.0.0-alpha02"

    implementation 'com.facebook.shimmer:shimmer:0.5.0'
    implementation 'com.todkars:shimmer-recyclerview:0.4.1'

    implementation 'de.hdodenhof:circleimageview:3.1.0'

    // Image Loading library Coil
    implementation "io.coil-kt:coil:0.13.0"

    implementation  'com.squareup.picasso:picasso:2.71828'

    // UI Tests
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.1.1'
    // When using a AppCompat theme
    implementation "com.google.accompanist:accompanist-appcompat-theme:0.16.0"
    implementation "com.google.android.material:compose-theme-adapter:1.1.1"

    // Lifecycle
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"

    implementation 'com.yqritc:android-scalablevideoview:1.0.4'

    implementation 'com.github.bumptech.glide:glide:4.11.0'
    kapt 'com.github.bumptech.glide:compiler:4.11.0'


    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.0'
    implementation("javax.inject:javax.inject:1")
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
    implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    testImplementation 'junit:junit:4. '
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // viewpager2
    implementation 'androidx.viewpager2:viewpager2:1.0.0'
    //tablayout
    implementation 'com.google.android.material:material:1.3.0-alpha04'

    // For developers using AndroidX in their applications
    implementation 'pub.devrel:easypermissions:3.0.0'

}

CodePudding user response:

You're creating two separate instances of ArtistsAdapter in MainActivity and VideoCallHomeFragment. It sounds like the adapter works fine if setData is working inside the fragment that's actually displaying the RecyclerView, because that's the one that has access to the actual adapter that's displaying the data.

But in searchArtist in MainActivity, you're calling setData on a completely different adapter instance that isn't connected to the RecyclerView in any way, so nothing's gonna happen.

Instead of having the activity trying to talk to a widget hosted in a fragment, it would be better to have a LiveData or similar in your view model that contains the data that's supposed to be displayed. Make VideoCallHomeFragment observe that, and it can call setData on the adapter. Your activity can call viewModel.getArtists() or whatever, but that function should internally update the LiveData so that anything observing it will see the new data to display.

  • Related