首次通话后无法观察LiveData

时间:2019-01-30 13:32:45

标签: mvvm kotlin android-viewmodel android-livedata

我实现了LiveData和ViewModel来模仿AsyncTaskLoader。

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

问题:

以下代码应借助LiveData异步地从DCIM / Pictures中获取文件名,然后将FileObserver附加到目录(DCIM / Pictures),以监视何时删除文件并使用如代码所示,LiveData子类可以重新加载文件。

好的,它是第一次工作,即第一次加载文件,调用setValue()并传递触发onChange的文件名,以在观察活动/片段中调用。但是,当删除文件时,回调函数将调用loadFiles()函数再次重新加载文件,但是这次调用setValue并传递FileNames不会触发观察活动/片段中的OnChange。

根据official documentation of LiveData

  

您必须调用setValue(T)方法来更新LiveData对象   从主线程开始。

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


代码

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 PictureDelete {
    fun onPicDelete()
}

我的实施有什么问题?

1 个答案:

答案 0 :(得分:0)

我这里有一个@Micklo_Nerd示例,但是它没有解决删除文件的问题,但是它为您提供了所需的操作思路。 在我的示例中,用户输入名称,然后单击按钮后,列表将更改。

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(在您的示例中是MyLiveData)

在这里,您必须完成获取文件夹中文件名的工作,并将其放入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

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

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

在这里,您看到我正在观察返回文件名列表的方法,这是您需要做的,因为这将要更改。

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

希望这会有所帮助。