第一次调用后未观察到 LiveData [英] LiveData not Observing after first Call

查看:32
本文介绍了第一次调用后未观察到 LiveData的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我实施了 LiveData &ViewModel 模仿 AsyncTaskLoader.

我从 DCIM 中的相机目录加载文件名,然后在删除文件(图片)时附加一个 fileObserver 到 Observe,然后回调告诉 LiveData 在删除事件发生时重新获取文件名

问题:

下面的代码应该在 LiveData 的帮助下从 DCIM/Pictures 中异步获取文件名,然后将 FileObserver 附加到目录 (DCIM/Pictures),以监控文件何时被删除,并使用LiveData 子类来重新加载文件,如代码所示.

好的,第一次工作,也就是第一次加载文件,调用setValue(),传入onChange触发的fileNames在观察Activity/Fragment中被调用.但是当一个文件被删除时,回调函数会调用loadFiles()函数再次重新加载文件,但是这次调用setValue并传入FileNames并不会触发观察Activity/Fragment中的OnChange.

根据LiveData官方文档

<块引用>

必须调用 setValue(T) 方法来更新 LiveData 对象来自主线程.

我很想知道为什么 LiveData 在第一次调用后没有更新其值.

<小时>

代码

MyLiveData

class MyLiveData() : MutableLiveData>(), PictureDelete {覆盖乐趣 onPicDelete() {加载文件名()}val 标签 = "MyLiveData"val fileNamesList: MutableList= 数组列表()val fileWatcher : MyFileWatcher在里面 {加载文件名()val pathToWatch = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Camera").getAbsolutePath()fileWatcher = MyFileWatcher(pathToWatch, this)fileWatcher.startWatching()}私人乐趣 loadFileNames() {val fileDir: 文件尝试 {fileDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Camera")} catch (e: 异常) {Log.e(TAG, e.message)返回}Log.d(标记,在状态 LiveData 中主动加载文件")val arrayOfFiles = fileDir.listFiles()if (arrayOfFiles == null || arrayOfFiles.size <1) 返回Log.d(TAG, "正在加载文件.大小:${arrayOfFiles.size}")设置值(文件名列表)}}

MyViewModel

class MyViewModel() : ViewModel() {val myLiveData: MyLiveDataval TAG = "WhatsAppFragment-VModel"在里面 {myLiveData = MyLiveData()}}

我的片段

class MyFragment : Fragment() {私人 val 标签 = "MyFragment"覆盖 fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?{val view = inflater.inflate(R.layout.fragment_layout, container, false)返回视图}覆盖 fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(视图,savedInstanceState)val viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)viewModel.myLiveData.observe(this, androidx.lifecycle.Observer { fileNames ->Log.d(TAG, "新的实时数据调度")for ((index, name) in fileNames.withIndex()) {Log.d(TAG, "$index 处的元素是 $name")}})}}

MyFileObserver

class MyFileWatcher(pathToWatch: String, val picDelete: PictureDelete) : FileObserver(pathToWatch, DELETE) {val TAG = "MyFileWatcher"在里面 {Log.d(标签,初始化")}覆盖 fun onEvent(event: Int, path: String?) {if (event = FileObserver.DELETE) {//事件代码 512 == 删除Log.d(TAG, "OnEvent.Event: $event Path: $path")picDelete.onPicDelete()}}}

图片删除界面

interface PictureDelete {有趣的 onPicDelete()}

我的实现有什么问题?

解决方案

我在这里有一个示例 @Micklo_Nerd 但它没有解决您删除文件的问题,但它为您提供了需要做什么的想法.在我的例子中,用户插入一个名字,点击一个按钮后,列表就会改变.

activity_main.xml

<按钮机器人:文本=添加"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"/><编辑文本android:layout_width="wrap_content"android:layout_height="wrap_content"android:inputType="textPersonName"机器人:ems =10"android:id="@+id/文件名"android:layout_marginStart="8dp"应用程序:layout_constraintStart_toStartOf="parent"android:layout_marginEnd="8dp"应用程序:layout_constraintEnd_toEndOf="父"android:layout_marginTop="32dp"app:layout_constraintTop_toTopOf="parent"/><文本视图android:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/textView"应用程序:layout_constraintStart_toStartOf="parent"android:layout_marginStart="8dp"应用程序:layout_constraintEnd_toEndOf="父"android:layout_marginEnd="8dp"android:layout_marginTop="16dp"app:layout_constraintTop_toBottomOf="@+id/buttonAdd"/></android.support.constraint.ConstraintLayout>

MyRepository(在您的示例中是 MyLiveData)

在这里,您必须完成获取文件夹中文件名的工作,并将其放入 MutableLiveData.

class MyRepository {fun loadFileNames(liveData : MutableLiveData>, filename: String){var fileList : MutableList?= liveData.value如果(测试 == 空)fileList = MutableList(1){ 文件名}别的fileList.add(文件名)liveData.value = 文件列表}}

MyViewModel

在这里,我有两种方法:一种是在单击按钮时更新列表,另一种是获取文件名列表.您可能只需要获取列表的那个

class MyViewModel : ViewModel() {val 仓库:MyRepositoryvar mutableLiveData : MutableLiveData>在里面 {回购 = MyRepository()mutableLiveData = MutableLiveData()}有趣的更改列表(文件名:字符串){repo.loadFileNames(mutableLiveData, 文件名)}fun getFileList() : MutableLiveData>{返回可变的LiveData}}

主活动

在这里,您看到我正在观察返回文件名列表的方法,这是您需要执行的操作,因为这将发生变化.

class MainActivity : AppCompatActivity(), View.OnClickListener {私人 val 标签 = "MyFragment"private lateinit var viewModel: MyViewModel覆盖 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> { fileNames ->Log.d(TAG, "新的实时数据调度")textView.text = ""for ((index, name) in fileNames!!.withIndex()) {textView.append("$index 处的元素是 $name
")}})buttonAdd.setOnClickListener(this)}覆盖乐趣 onClick(v:查看?){当(v!!.id){R.id.buttonAdd ->{viewModel.changeList(filename.text.toString())文件名.text.clear()}}}}

希望这会有所帮助.

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?

解决方案

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
")
         }
      })

      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.

这篇关于第一次调用后未观察到 LiveData的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆