LiveData не отслеживает после первого вызова

Я реализовал LiveData и ViewModel для имитации AsyncTaskLoader.

Я загружаю имена файлов из каталога камеры в DCIM, а затем прикрепляю fileObserver для наблюдения за удалением файла (изображения), а затем обратный вызов сообщает LiveData повторно получить имена файлов при возникновении события удаления.

Проблема:

Приведенный ниже код должен асинхронно извлекать имена файлов из DCIM / Pictures с помощью LiveData, а затем к каталогу (DCIM / Pictures) присоединяется FileObserver, чтобы отслеживать, когда файл удаляется, и выполняется обратный вызов с помощью подгруппы LiveData. класс, чтобы перезагрузить файлы, как показано в коде.

хорошо, он работает в первый раз, то есть файлы загружаются в первый раз, вызывая setValue() и передавая имена файлов, запускаемые onChange для вызова в наблюдаемом Activity / Fragment. Но когда файл удаляется, функция обратного вызова вызывает функцию loadFiles () для повторной загрузки файлов, но вызов setValue и передача FileNames на этот раз не запускают OnChange в наблюдаемой Activity / Fragment.

Согласно официальной документации 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()
}

Что не так с моей реализацией?


person LekeOpe    schedule 30.01.2019    source источник
comment
Я предполагаю, потому что MyFileWatcher прослушивается, но мы не видим этого, не так ли?   -  person EpicPandaForce    schedule 30.01.2019
comment
Собственно, работает. Проверено на Android 9.0   -  person LekeOpe    schedule 30.01.2019
comment
@EpicPandaForce MyFileObserver теперь там.   -  person LekeOpe    schedule 30.01.2019
comment
Вы можете использовать if (event == FileObserver.DELETE), но, видимо, это кажется правильным. Хм. Код кажется правильным, поэтому я готов поспорить, что с FileObserver что-то не так, но я не понимаю, почему он не работает. : |   -  person EpicPandaForce    schedule 30.01.2019
comment
В fileObserver ничего плохого нет, работает хорошо. Беги на своем конце!   -  person LekeOpe    schedule 30.01.2019
comment
Вы наблюдаете за переменной. Попробуйте понаблюдать за методом, возвращающим эту переменную. Это просто идея, не знаю, сработает ли она ...   -  person joao86    schedule 31.01.2019
comment
и как мне это сделать?   -  person LekeOpe    schedule 31.01.2019
comment
так что попробуйте понаблюдать за методом, который возвращает список имен файлов. Когда вы удаляете файл, обновляйте список имен файлов, затем, когда вы обновляете список, данные в реальном времени будут активированы. Я могу попробовать написать код позже сегодня или завтра, но вы можете попробовать это сделать.   -  person joao86    schedule 01.02.2019
comment
@Micklo_Nerd, я думаю, проблема в том, что вы должны следить за списком имен файлов, потому что это то, что он собирается изменить.   -  person joao86    schedule 01.02.2019
comment
@ joao86 не могли бы вы показать мне в коде, что вы имеете в виду?   -  person LekeOpe    schedule 02.02.2019


Ответы (1)


У меня есть пример @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()
        }
      }
   }
}

Надеюсь это поможет.

person joao86    schedule 02.02.2019
comment
Спасибо за ваш вклад. Позвольте мне быстро запустить его. - person LekeOpe; 18.02.2019