Home > Back-end >  android my viewModel update the list internally but recyclerview does't reflects the changes
android my viewModel update the list internally but recyclerview does't reflects the changes

Time:02-16

I'm trying to view my files using recycler view and MVVM, the problem is that memoryViewModel doesn't reflect the changes on memoryItemsRecyclerViewAdapter

memoryViewModel!!.mutableLiveData?.observe(this.viewLifecycleOwner) {
            memoryItemsRecyclerViewAdapter.notifyDataSetChanged() //this should show the list items of the recycler view
            Log.d("view model result", it.size.toString()) // log message shows the list items
        }

so, the final question is why the mutableLiveData!!.postValue(mutableList) don't update the recycler view? here is a sample of my code

the fragment

class MemoryFragment: Fragment() {
    companion object{
        private var rootDirectory: java.io.File? = null
        var currentFolder = rootDirectory
        private var filesList: MutableList<File?>? = mutableListOf()
        private var memoryViewModel: MemoryViewModel? = null
    }
    private var sharedPreferences: SharedPreferences? = null
    private lateinit var listView: RecyclerView
    private lateinit var pathsRecyclerView: RecyclerView
    private lateinit var searchView: androidx.appcompat.widget.SearchView
    private lateinit var refreshSwipe: SwipeRefreshLayout
    private lateinit var memoryItemsRecyclerViewAdapter: MemoryItemsRecyclerViewAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sharedPreferences = androidx.preference.PreferenceManager
            .getDefaultSharedPreferences(requireContext())

        val memoryViewModelProvider = MemoryViewModelProvider()
        memoryViewModel = ViewModelProvider(this, memoryViewModelProvider).get(MemoryViewModel::class.java)
        
//        rootDirectory = Environment.getExternalStorageDirectory()
//        currentFolder = rootDirectory
//        val rootFoldersList = rootDirectory?.listFiles()?.toMutableList()
//        for (item in rootFoldersList!!)
//            if(item.isDirectory)
//                filesList?.add(File(R.drawable.ic_folders, item, item.totalSpace))
//            else
//                filesList?.add(File(R.drawable.ic_file, item, item.totalSpace))
//        memoryViewModel = MemoryViewModel(filesList!!)
//        MemoryViewModel.lazyMemoryViewModel
    }
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = LayoutInflater.from(requireContext()).inflate(R.layout.fragment_memory, container, false)
        listView = view.findViewById(R.id.list_view)
        pathsRecyclerView = view.findViewById(R.id.recycler_view)
        searchView = view.findViewById(R.id.search_view)
        refreshSwipe = view.findViewById(R.id.swipe_refresh)
        listView.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)

        memoryItemsRecyclerViewAdapter = MemoryItemsRecyclerViewAdapter(memoryViewModel!!, requireContext(), filesList!!)

        listView.adapter = memoryItemsRecyclerViewAdapter

        memoryViewModel!!.mutableLiveData?.observe(this.viewLifecycleOwner) {
            memoryItemsRecyclerViewAdapter.notifyDataSetChanged()
            Log.d("view model result", it.size.toString())

        }
        return view
    }
}

the viewModel

class MemoryViewModel(private var mutableList: MutableList<com.example.everyentertainment.models.File?>?): ViewModel() {
private val foldersPath: MutableList<String?>? = null
private val foldersName: MutableList<String?>? = null
private var rootDirectory: File? = null
var mutableLiveData: MutableLiveData<MutableList<com.example.everyentertainment.models.File?>>? = null

companion object{
    var currentFolder: File? = null
}

init {
    val job = viewModelScope.launch(Dispatchers.IO) {
        if (mutableLiveData == null) mutableLiveData = MutableLiveData()
        if(mutableList == null) mutableList = mutableListOf()
        initializeMemoryFragment()
    }
    job.start()
    job.invokeOnCompletion {
        mutableLiveData!!.postValue(mutableList)//postValue() doesn't update UI also I tried mutableLiveData.value = mutableList but it throws OnCompletionHandlerException
        Log.d("mutable list size", mutableList!!.size.toString())
        Log.d("mutable live data size", mutableLiveData!!.value.toString())
    }
}

fun initializeMemoryFragment(){
    rootDirectory = getExternalStorageDirectory()
    currentFolder = rootDirectory
    val filesList = rootDirectory!!.listFiles()
    for(position in filesList!!.indices)
        if(filesList[position].isDirectory)
            mutableList!!.add(com.example.everyentertainment.models.File(R.drawable.ic_folders,
                filesList[position], getFolderSize(filesList[position])))
        else
            mutableList!!.add(com.example.everyentertainment.models.File(R.drawable.ic_file,
                filesList[position], getFolderSize(filesList[position])))
}

 fun getFolderSize(file: File): Long{
    if(!file.exists())
        return 0
    if(!file.isDirectory)
        return file.length()
    val dirs: MutableList<File> = LinkedList()
    dirs.add(file)
    var result: Long = 0
    while (!dirs.isEmpty()) {
        val dir = dirs.removeAt(0)
        if (!dir.exists()) continue
        val listFiles = dir.listFiles()
        if (listFiles == null || listFiles.isEmpty()) continue
        for (child in listFiles) {
            result  = child.length()
            if (child.isDirectory) dirs.add(child)
        }
    }
    return result
}

and finally the recycler view adapter

class MemoryItemsRecyclerViewAdapter(private val memoryViewModel: MemoryViewModel, private val context: Context, private val dataSet: MutableList<File?>?):
RecyclerView.Adapter<MemoryItemsRecyclerViewAdapter.ViewHolder>() {
/**
 * Provide a reference to the type of views that you are using
 * (custom ViewHolder).
 */
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val fileNameTextView: TextView = view.findViewById(R.id.name_text_view)
    val sizeTextView: TextView = view.findViewById(R.id.size_text_view)
    val numberOfFilesTextView: TextView = view.findViewById(R.id.number_of_files_text_view)
    val dateTextView: TextView = view.findViewById(R.id.date_text_view)
    val imageView: ImageView = view.findViewById(R.id.image_view)

    init {
    // Define click listener for the ViewHolder's View.

    }
}

// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
    // Create a new view, which defines the UI of the list item
    val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.memory_list_view_item, viewGroup, false)
    return ViewHolder(view)
}

// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
    // Get element from your dataset at this position and replace the
    // contents of the view with that element
    val file = dataSet!![position]
    viewHolder.fileNameTextView.text = file!!.file.name
    viewHolder.sizeTextView.text = memoryViewModel.readableFileSize(dataSet[position]!!.size)
    viewHolder.dateTextView.text = memoryViewModel.getFolderDateModified(file.file)
    viewHolder.numberOfFilesTextView.text = memoryViewModel.getSubFoldersQuantity(context, file.file)
    if(file.file.isDirectory)
        viewHolder.imageView.setImageResource(R.drawable.ic_folders)
    else
        viewHolder.imageView.setImageResource(R.drawable.ic_file)
}

// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = dataSet!!.size

}

CodePudding user response:

Your adapter is hardcoded to display the contents of dataSet (you fetch from it in onBindViewHolder, you reference it in getItemCount) and you never change those contents, so there's never anything to update. The RecyclerView will only ever display what you first passed in. You need to make the adapter's data updateable, I'd recommend something like this:

class MemoryItemsRecyclerViewAdapter(
    private val memoryViewModel: MemoryViewModel,
    private val context: Context,
    private var dataSet: List<File?> = emptyList() // this is a var now, and not a mutable list
) : RecyclerView.Adapter<MemoryItemsRecyclerViewAdapter.ViewHolder>() {

    // this function replaces the current data with the new set, and refreshes the UI
    fun setData(newData: List<File?>) {
        dataSet = newData
        notifyDataSetChanged()
    }

    ...
}

Now you have a way to update the adapter, and it takes care of updating the UI itself - it's better to have the logic (like calling notifyDataSetChanged()) in here, because really the adapter should be deciding what's changed and how to handle it.

I made dataSet a var so you can just swap the old list for the new one, and made them immutable Lists since they don't need to be mutable. I added emptyList() as a default - it doesn't need to be nullable, and you're treating it as non-null (with !! everywhere) anyway, so just make it non-null. If you really want, you can make it a mutable list and do it that way - in that case, use mutableListOf() for the empty default instead


So now you can update the adapter, you just need to do that in your observer function:

memoryViewModel!!.mutableLiveData?.observe(this.viewLifecycleOwner) { newData ->
    // when a new list comes in, set it on the adapter
    memoryItemsRecyclerViewAdapter.setData(newData)
    Log.d("view model result", newData.size.toString())
}

and that's it. Your observer just reacts to new values as they come in (which includes any current value, when you first observe the LiveData) so you just need to do whatever with them

(Also, you're really complicating things by making everything nullable, especially when you're assuming they're all non-null when you access them anyway with !!)

  • Related