0

Update: how to use ViewModelFactory and is it necessary to use this for passing our parameter? What's the benefit? is that going to break live data concept?

I want to send a parameter to my word Dao of my room database for query but in my case, I don't know how to pass that parameter. so let's begin with codes... WordDao.kt

@Dao
interface WordDao {

    @Insert
    fun insert(word: Word)

    @Update
    fun update(word: Word)

    @Delete
    fun delete(word: Word)

    @Query("delete from En_Fa")
    fun deleteAllNotes()

    @Query("SELECT * FROM En_Fa ORDER BY id ASC")
    fun getAllNotes(): LiveData<List<Word>>

    @Query("Select * From En_Fa WHERE date == :today ")
    fun getTodayWords(today: String): LiveData<List<Word>>
}

WordRepository.kt

class WordRepository(private val wordDao: WordDao, today: String) {

    val readAllData: LiveData<List<Word>> = wordDao.getAllNotes()
    val readToday: LiveData<List<Word>> = wordDao.getTodayWords(today)


    fun addWord(word: Word) {
        wordDao.insert(word)
    }
}

WordViewModel.kt

class WordViewModel(application: Application): AndroidViewModel(application) {

    val readAllData2: LiveData<List<Word>>

    private val repository: WordRepository

    init {

        val wordDao = WordDatabase.getInstance(application).wordDao()

        repository = WordRepository(wordDao, today)
        readAllData2 = repository.readToday

    }

    fun addWord(word: Word){
        viewModelScope.launch(Dispatchers.IO){
            repository.addWord(word)
        }
    }

}

and this is the line of my code that make an object of this wordview model class in my fragment

private val mWordViewModel: WordViewModel by viewModels()

so how to pass my (today) variable from my fragment to my WordViewModel class

Morteza
  • 129
  • 1
  • 12

2 Answers2

2

I think you are looking for this:


@Dao
interface WordDao {
// ...
    @Query("Select * From En_Fa WHERE date == :today ")
    fun getTodayWords2(today: String): <List<Word>
}

Then in the repository:

class WordRepository{
  // ... ...
  var mutableWords: MutableLiveData<List<Word>> = MutableLiveData()

  fun getWords(today: String): List<Word> {  // WARNING! run this in background thread else it will crash
    return wordDao.getTodayWords2(today)
  }

  fun getWordsAsync(today: String) {
   Executors.newSingleThreadExecutor().execute { 
          val words = getWords(today)
          liveWords.postValue(words)   // <-- just doing this will trigger the observer and do next thing, such as, updating ui
        }
  }
}

Then in your viewModel:

class WordViewModel(application: Application): AndroidViewModel(application) {
  // ... ...

  val liveWords: LiveData<List<Word>> = repository.mutableWords

  fun getWordsAsync(today: String) {
    repository.getWordsAsync(today)
  }
}

Then finally inside your activity / fragment:

fun viewModelDemo() {
  mWordViewModel.liveWords.observe(this, Observer{
    // todo: update the ui, eg
    someTextView.text = it.toString()    // <-- here you get the output
   })


   someButton.setOnClickListener{
// here you give the input
  mWordViewModel.getWordsAsync(today)  // get `today` from date picker or something    
   }
}

Edit

So you have a recyclerView which has an adapter. When the dataset changes, you call notifyDataSetChanged. Suppose, the adapter looks like this:

class MyAdapter: RecyclerView.Adapter<ViewHolder> {
  private var words: List<Word> = ArrayList() // initially points to an empty list

  override fun getCount() { return words.size }
  // ... ... other methods

  // public method:
  fun submitList(words1: List<Word>) {
    this.words = words1  // so now it points to the submitted list
    this.notifyDataSetChanged()  // this tells recyclerView to update itself
  }  

}

Then in your activity or fragment:

private lateinit var myAdapter: MyAdapter

override fun onCreate() {  // or onViewCreated if using fragment
  // ... ... some codes
  this.myAdapter = MyAdapter()
  binding.recyclerView.adapter = myAdapter

  viewModelDemo()
}

fun viewModelDemo() {
  mWordViewModel.liveWords.observe(this, Observer{
    // todo: update the ui, eg
    myAdapter.submitList(it)    // <----- Here you call the submitList method

    // <-- here you get the output
   })
  // --- ---
}

I hope this works.

Qazi Fahim Farhan
  • 2,066
  • 1
  • 14
  • 26
  • so I was on something these days till now I came back to my code and tried your code. it worked but not quiet. I'm new at live data and I think your code is breaking the live data concept. when I go to my dashboard fragment, it shows me my database but when I add a word to it, doesn't show it till I change the fragment and back again, before this, the recycler view updating itself immediately and when I added a word it shows that in moment – Morteza Jun 28 '21 at 17:09
  • did you call `adapter.notifyDataSetChanged()` ? – Qazi Fahim Farhan Jun 28 '21 at 18:22
  • where should i use it? can you show in your answer? – Morteza Jun 28 '21 at 20:01
  • I think the problem is mutable Live Data. I searched and others ran into this problem too. but we set lifeCycleOwner! – Morteza Jun 28 '21 at 20:21
  • Hello. I have edited my answer and added some code as requested. Please check the bottom part o my answer, – Qazi Fahim Farhan Jun 28 '21 at 21:13
  • I had the things you mentioned in adapter class such as that notifyDataSetChanhed method, but I can't use binding in my fragment and don't know how to use it, it's an Unresolved reference. even before you mention that binding I wanted to use that, but why was my recyclerView updating itself immediately before? this was my code ***mWordViewModel.readAllData2.observe(viewLifecycleOwner, { word -> adapter.setData(word) })*** – Morteza Jun 29 '21 at 10:57
  • Binding is simply an alternative to this code: `val myRecyclerView = findViewById(R.id.yourRecyclerView); myRecycerView.adapter = myAdapter`, don't worry about binding, just do what you are used to doing. I am sorry, but at this point, I really don't understand what went wrong with liveData. Could you share your code via github / any other way? – Qazi Fahim Farhan Jun 29 '21 at 11:26
  • I can share my code with GitHub pls write your username or something to add you to the repository, thanks for helping – Morteza Jun 29 '21 at 12:18
  • My github profile is [fahimfarhan](https://github.com/fahimfarhan) – Qazi Fahim Farhan Jun 29 '21 at 13:25
  • check your emails – Morteza Jun 29 '21 at 13:48
  • check the code on github. The problem was, you only inserted word inside database, but you didnot update the mutableLiveData> inside the repository. That's why the change didnot take place immidiately. So after inserting word, I simply updated mutableLiveData. That solved the problem. – Qazi Fahim Farhan Jun 29 '21 at 14:55
  • you were right. thanks to you, it's working now. – Morteza Jun 29 '21 at 15:22
0

If i get your question correctly then, First, you need to make a function that will return the liveData object to observe, then you need to use ViewModelProviders that will provide you the object of your ViewModel in your fragment.

mWordViewModel: WordViewModel = ViewModelProvider(this/getActivity()).get(WordViewModel::class.java)
mWordViewModel.getLiveDataFunction().observe(this/lifeCycleOwner, {
    process result/response here
}

Then simply use.

mWordViewModel.addWord(today)
SRB Bans
  • 3,096
  • 1
  • 10
  • 21
  • This will be done everytime screen is rotated though – Rahul Rawat Jun 27 '21 at 10:18
  • @RahulRawat If the activity is re-created, it receives the same MyViewModel instance that was created by the first activity. When the owner activity is finished, the framework calls the ViewModel objects's onCleared() method so that it can clean up resources. see this https://developer.android.com/topic/libraries/architecture/viewmodel#implement – SRB Bans Jun 27 '21 at 10:50
  • I know that. It looks like Morteza needs the addWord today to be called once when the ViewModel is created which is why the question has it being added inside init – Rahul Rawat Jun 27 '21 at 11:19
  • @SRBBans I cant use your first line code, it shows me the error "Unresolved reference." – Morteza Jun 28 '21 at 17:16
  • @RahulRawat I want to pass the parameter without breaking the live data concept. – Morteza Jun 28 '21 at 17:18