问题
I implemented LiveData & ViewModel to mimic AsyncTaskLoader.
I load file names from the camera directory in DCIM, and then i attach a fileObserver to Observe when a File (picture) is deleted, and then a callback tells the LiveData to re-fetch the fileNames when delete event occurs
The Problem:
The code below should fetch the file Names from DCIM/Pictures asynchronously with the help of LiveData and then a FileObserver is attached to the directory (DCIM/Pictures), to monitor when a file is deleted and a callback is implemented with the LiveData sub-class to reload the files, as demonstrated in code.
okay, it works the first time, that is, the files are loaded the first time, calling setValue()
and passing the fileNames triggered onChange to be called in the observing Activity/Fragment. But when a file is deleted, the callback function calls the loadFiles() function to re-load the files again but calling the setValue and passing in the FileNames does not trigger OnChange in the observing Activity/Fragment this time around.
According to the official documentation of LiveData
You must call the setValue(T) method to update the LiveData object from the main thread.
I am curious to know why LiveData is not updating its value after the first call.
The Code
MyLiveData
class MyLiveData() : MutableLiveData<MutableList<String>>(), PictureDelete {
override fun onPicDelete() {
loadFileNames()
}
val TAG = "MyLiveData"
val fileNamesList: MutableList<String> = ArrayList()
val fileWatcher : MyFileWatcher
init {
loadFileNames()
val pathToWatch = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Camera").getAbsolutePath()
fileWatcher = MyFileWatcher(pathToWatch, this)
fileWatcher.startWatching()
}
private fun loadFileNames() {
val fileDir: File
try {
fileDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Camera")
} catch (e: Exception) {
Log.e(TAG, e.message)
return
}
Log.d(TAG, "Actively Loading Files in Status LiveData")
val arrayOfFiles = fileDir.listFiles()
if (arrayOfFiles == null || arrayOfFiles.size < 1) return
Log.d(TAG, "Actively Loading Files. Size: ${arrayOfFiles.size}")
setValue(fileNamesList)
}
}
MyViewModel
class MyViewModel() : ViewModel() {
val myLiveData: MyLiveData
val TAG = "WhatsAppFragment-VModel"
init {
myLiveData = MyLiveData()
}
}
MyFragment
class MyFragment : Fragment() {
private val TAG = "MyFragment"
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_layout, container, false)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
viewModel.myLiveData.observe(this, androidx.lifecycle.Observer { fileNames ->
Log.d(TAG, "New Live Data Dispatch")
for ((index, name) in fileNames.withIndex()) {
Log.d(TAG, "the element at $index is $name")
}
})
}
}
MyFileObserver
class MyFileWatcher(pathToWatch: String, val picDelete: PictureDelete) : FileObserver(pathToWatch, DELETE) {
val TAG = "MyFileWatcher"
init {
Log.d(TAG, "Initialization")
}
override fun onEvent(event: Int, path: String?) {
if (event = FileObserver.DELETE) { // EventCode 512 == Delete
Log.d(TAG, "OnEvent. Event: $event Path: $path")
picDelete.onPicDelete()
}
}
}
PictureDelete Interface
interface PictureDelete {
fun onPicDelete()
}
What is wrong with my Implementation?
回答1:
I have here an example @Micklo_Nerd but it does not use your problem of getting the files deleted but it gives you the idea for what you need to do. In my example the user insert a name and after clicking a button, the list is changed.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:text="Add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/buttonAdd"
app:layout_constraintStart_toStartOf="@+id/filename"
app:layout_constraintEnd_toEndOf="@+id/filename"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="@+id/filename"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:ems="10"
android:id="@+id/filename"
android:layout_marginStart="8dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/buttonAdd"/>
</android.support.constraint.ConstraintLayout>
MyRepository (In your example is the MyLiveData)
In here you must do the work of getting the filenames in the folder, and put the in the MutableLiveData.
class MyRepository {
fun loadFileNames(liveData : MutableLiveData<MutableList<String>>, filename: String){
var fileList : MutableList<String>? = liveData.value
if(test == null)
fileList = MutableList(1){ filename }
else
fileList.add(filename)
liveData.value = fileList
}
}
MyViewModel
In here, I have two methods: one to update the list as I click the button and another to get the list of file names. You should probably only need the one that gets the list
class MyViewModel : ViewModel() {
val repo: MyRepository
var mutableLiveData : MutableLiveData<MutableList<String>>
init {
repo = MyRepository()
mutableLiveData = MutableLiveData()
}
fun changeList(filename: String){
repo.loadFileNames(mutableLiveData, filename)
}
fun getFileList() : MutableLiveData<MutableList<String>>{
return mutableLiveData
}
}
MainActivity
In here, you see I am observing the method that returns the list of filenames, which is what you need to do, because that is what is going to change.
class MainActivity : AppCompatActivity(), View.OnClickListener {
private val TAG = "MyFragment"
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
viewModel.getFileList().observe(this, Observer<MutableList<String>> { fileNames ->
Log.d(TAG, "New Live Data Dispatch")
textView.text = ""
for ((index, name) in fileNames!!.withIndex()) {
textView.append("the element at $index is $name\n")
}
})
buttonAdd.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v!!.id){
R.id.buttonAdd -> {
viewModel.changeList(filename.text.toString())
filename.text.clear()
}
}
}
}
Hope this helps.
来源:https://stackoverflow.com/questions/54441785/livedata-not-observing-after-first-call