android 我的 viewModel 在内部更新列表,但 recyclerview 不反映更改
android my viewModel update the list internally but recyclerview does't reflects the changes
我正在尝试使用回收器视图和 MVVM 查看我的文件,问题是 memoryViewModel
没有反映 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
}
所以,最后一个问题是为什么 mutableLiveData!!.postValue(mutableList)
不更新回收站视图?
这是我的代码示例
片段
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
}
}
视图模型
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
}
最后是回收器视图适配器
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
}
您的适配器被硬编码为显示 dataSet
的内容(您在 onBindViewHolder
中获取它,在 getItemCount
中引用它)并且您永远不会更改这些内容,所以有从来没有什么要更新的。 RecyclerView
只会显示您第一次传入的内容。您需要使适配器的数据可更新,我建议这样做:
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()
}
...
}
现在您有了更新适配器的方法,它负责更新 UI 本身 - 最好在此处包含逻辑(如调用 notifyDataSetChanged()
),因为确实适配器应该决定发生了什么变化以及如何处理它。
我制作了 dataSet
一个 var
这样你就可以将旧列表换成新列表,并使它们不可变 List
因为它们不需要可变.我将 emptyList()
添加为默认值 - 它不需要可以为 null,并且您将其视为 non-null (到处都是 !!
),所以只需将其设为 non-null.如果你真的想要,你可以使它成为一个可变列表并这样做 - 在这种情况下,使用 mutableListOf()
作为空默认值而不是
所以现在你可以更新适配器了,你只需要在你的观察者函数中这样做:
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())
}
就是这样。你的观察者只是在新值进来时做出反应(包括任何当前值,当你第一次 observe
和 LiveData
时)所以你只需要对它们做任何事情
(此外,通过使所有内容都可为空,您确实使事情变得复杂,尤其是当您假设它们都是 non-null 而无论如何您都可以使用 !!
访问它们时)
我正在尝试使用回收器视图和 MVVM 查看我的文件,问题是 memoryViewModel
没有反映 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
}
所以,最后一个问题是为什么 mutableLiveData!!.postValue(mutableList)
不更新回收站视图?
这是我的代码示例
片段
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
}
}
视图模型
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
}
最后是回收器视图适配器
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
}
您的适配器被硬编码为显示 dataSet
的内容(您在 onBindViewHolder
中获取它,在 getItemCount
中引用它)并且您永远不会更改这些内容,所以有从来没有什么要更新的。 RecyclerView
只会显示您第一次传入的内容。您需要使适配器的数据可更新,我建议这样做:
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()
}
...
}
现在您有了更新适配器的方法,它负责更新 UI 本身 - 最好在此处包含逻辑(如调用 notifyDataSetChanged()
),因为确实适配器应该决定发生了什么变化以及如何处理它。
我制作了 dataSet
一个 var
这样你就可以将旧列表换成新列表,并使它们不可变 List
因为它们不需要可变.我将 emptyList()
添加为默认值 - 它不需要可以为 null,并且您将其视为 non-null (到处都是 !!
),所以只需将其设为 non-null.如果你真的想要,你可以使它成为一个可变列表并这样做 - 在这种情况下,使用 mutableListOf()
作为空默认值而不是
所以现在你可以更新适配器了,你只需要在你的观察者函数中这样做:
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())
}
就是这样。你的观察者只是在新值进来时做出反应(包括任何当前值,当你第一次 observe
和 LiveData
时)所以你只需要对它们做任何事情
(此外,通过使所有内容都可为空,您确实使事情变得复杂,尤其是当您假设它们都是 non-null 而无论如何您都可以使用 !!
访问它们时)