I'm trying to retrieve a single object from my room database given its title but when I get to the fragment where I want this to happen, I have no way to access the object's fields. The method I'm trying to use is retrieveMovie but the observer shows that it = List! and for some reason I cannot get the movie given its title and then get its fields to bind them to my view. How can I do this?
I tried accessing the movie with it's index(0) but the second movie I click on causes an app crash that returns: 'java.lang.IndexOutOfBoundsException: Index: 0, Size: 0'
This is the fragment's part:
if(arguments != null){
val titleString = arguments?.getString("Title")
//observe viewmodel
if (titleString != null) {
mMoviesViewModel.retrieveMovie(titleString).observe(viewLifecycleOwner, Observer { movie ->
itemTextTitle.text = movie.title //this doesn't work
})
}
} else {
//display error message if arguments are null
Toast.makeText(context, "Error loading content", Toast.LENGTH_SHORT).show()
}
This is the db's viewmodel:
class MoviesViewModel(application: Application): AndroidViewModel(application) {
val readAllData: LiveData<List<Movies>>
private val repository: MoviesRepository
init {
val moviesDao = MoviesDatabase.getDatabase(application).moviesDao()
repository = MoviesRepository(moviesDao)
readAllData = repository.readAllData
}
fun addMovie(movie: Movies) {
viewModelScope.launch(Dispatchers.IO) {
repository.addMovie(movie)
}
}
fun movieExists(id: Int): Boolean{
viewModelScope.launch(Dispatchers.IO){
repository.movieExists(id)
}
return true
}
fun retrieveMovie(title: String): LiveData<List<Movies>> {
return repository.retrieveMovie(title)
}
}
This is the db's repository:
class MoviesRepository (private val moviesDao: MoviesDao) {
val readAllData: LiveData<List<Movies>> = moviesDao.readALlData()
fun addMovie(movie: Movies){
moviesDao.addMovie(movie)
}
fun movieExists(id:Int){
moviesDao.movieExists(id)
}
fun retrieveMovie(title:String): LiveData<List<Movies>> {
return moviesDao.retrieveMovie(title)
}
}
Dao:
package com.app.challengemovieapp.db
import androidx.lifecycle.LiveData
import androidx.room.*
import com.app.challengemovieapp.model.Movie
import kotlinx.coroutines.flow.Flow
#Dao
interface MoviesDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun addMovie(movie:Movies)
#Query("SELECT * FROM movie_table ORDER BY id ASC")
fun readALlData(): LiveData<List<Movies>>
#Query("SELECT EXISTS(SELECT * FROM movie_table WHERE id = :id)")
fun movieExists(id : Int) : Boolean
#Query("SELECT * FROM movie_table WHERE title = :title")
fun retrieveMovie(title:String): LiveData<List<Movies>>
}
To get a single entity you can use limit. Here is an example
#Query("SELECT * FROM movie_table WHERE title = :title LIMIT 1")
fun retrieveMovie(title:String): LiveData<Movies>
so the reason why you can't access the Movie object is cause you are mentioning this.
#Query("SELECT * FROM movie_table WHERE title = :title")
fun retrieveMovie(title:String): LiveData<List<Movies>>.
So LiveData is returning a List of Movie objects.
what you can do is
it.get(0).getTitle()
It should work.
Related
I have a ShoppingList App that is built on MVVM architecture Android.
I did not make it, but I followed a tutorial on Youtube.
This is the image of the app(1/2) where the shopping list is shown. The bottom right side is a button for adding new elements of the list.
This is the second view(2/2) where the dialog appears to enter a name of our element and amount of it. Here we have cancel button and add button.
The problem is when I click the ADD button on the Dialog Box I do not know how to get an ID of this added item to the recycler view on my VIEW and to make it appear via the TOAST command on my main Activity.
The question is - How to get an ID of a new added element to my shopping list and show it on my MainActivity(ShoppingActivity) VIEW when I click the ADD button?
If you need additional information ask me out immediately! I will provide you anything you need.
Code is provided here:
ShoppingActivity(View)
class ShoppingActivity : AppCompatActivity(), KodeinAware {
override val kodein by kodein()
private val factory: ShoppingViewModelFactory by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_shopping)
// View Model is being created out of other classes to set changes to View
val viewModel = ViewModelProviders.of(this, factory).get(ShoppingViewModel::class.java)
// Adapters and Recycler View
val adapter = ShoppingItemAdapter(listOf(), viewModel)
rvShoppingItems.layoutManager = LinearLayoutManager(this)
rvShoppingItems.adapter = adapter
// ViewModel makes changes to the Activity
viewModel.getAllShoppingItems().observe(this, Observer {
adapter.items = it
adapter.notifyDataSetChanged()
})
fab.setOnClickListener {
AddShoppingItemDialog(this ,
object: AddDialogListener{
override fun onAddButtonClicked(item: ShoppingItem) {
viewModel.upsert(item)
showToast(viewModel.getID(item).toString().toInt())
}
}).show()
}
}
fun showToast(id: Int) {
Toast.makeText(this#ShoppingActivity, "ID записи: $id", Toast.LENGTH_LONG).show()
}}
ShoppingViewModel(ViewModel)
class ShoppingViewModel(private val repository: ShoppingRepository): ViewModel() {
fun upsert(item: ShoppingItem) = CoroutineScope(Dispatchers.IO).launch {
repository.upsert(item)
}
fun delete(item: ShoppingItem) = CoroutineScope(Dispatchers.IO).launch {
repository.delete( item)
}
fun getID(item: ShoppingItem) = repository.getID(item)
fun getAllShoppingItems() = repository.getAllShoppingItems()
}
AddShoppingItemDialog(the logic of showing Dialog info)
class AddShoppingItemDialog(context: Context, var addDialogListener: AddDialogListener): AppCompatDialog(context) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.dialog_add_shopping_item)
tvAdd.setOnClickListener {
val name = etName.text.toString()
val amount = etAmount.text.toString()
if(name.isEmpty()) {
Toast.makeText(context, "Please enter the name", Toast.LENGTH_LONG).show()
return#setOnClickListener
}
if(amount.isEmpty()) {
Toast.makeText(context, "Please enter the amount", Toast.LENGTH_LONG).show()
return#setOnClickListener
}
val item = ShoppingItem(name, amount.toInt())
// We need to
addDialogListener.onAddButtonClicked(item)
dismiss()
}
tvCancel.setOnClickListener {
cancel()
}
}}
Repository
class ShoppingRepository(private val db: ShoppingDatabase) {
suspend fun upsert(item: ShoppingItem) = db.getShoppingDao().upsert(item)
suspend fun delete(item: ShoppingItem) = db.getShoppingDao().delete(item)
fun getID(item: ShoppingItem) = db.getShoppingDao().getID(item)
fun getAllShoppingItems() = db.getShoppingDao().getAllShoppingItems()}
ShoppingDAO
#Dao
interface ShoppingDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(item: ShoppingItem) : Long
#Delete
suspend fun delete(item: ShoppingItem)
#Query("SELECT * FROM shopping_items WHERE id = $CURRENT_POSITION")
fun getID(item: ShoppingItem): LiveData<Int>
#Query("SELECT * FROM shopping_items")
fun getAllShoppingItems(): LiveData<List<ShoppingItem>>
}
ShoppingItem
const val CURRENT_POSITION = 0
#Entity(tableName = "shopping_items")
data class ShoppingItem(
#ColumnInfo(name = "item_name")
var name: String,
#ColumnInfo(name = "item_amount")
var amount: Int
) {
#PrimaryKey(autoGenerate = true)
var id: Int? = CURRENT_POSITION
}
AddDialogListener
interface AddDialogListener {
fun onAddButtonClicked(item: ShoppingItem)
}
App View with added items
Since insert/upsert operations on Database are suspend functions, observe returned id in view model
In ShoppingViewModel
private var _itemId : Long = MutableLiveData<Long>()
val itemId : LiveData<Long>
get() = _itemId
fun upsert(item: ShoppingItem) = CoroutineScope(Dispatchers.IO).launch {
val id = repository.upsert(item)
_itemId.postValue(id)
}
In ShoppingActivity,
viewModel.itemId.observe(this, Observer {id ->
showToast(id)
})
Please let me know if you need to know more details.
You need to use SingleLiveEvent to observe the value only once.
Add this SingleLiveEvent class
/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages.
*
*
* This avoids a common problem with events: on configuration change (like rotation) an update
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call().
*
*
* Note that only one observer is going to be notified of changes.
*/
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
#MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}
companion object {
private val TAG = "SingleLiveEvent"
}
}
In ShoppingViewModel
Replace this
private var _itemId : Long = MutableLiveData<Long>()
val itemId : LiveData<Long>
get() = _itemId
with
private var _itemId : Long = SingleLiveEvent<Long>()
val itemId : LiveData<Long>
get() = _itemId
You can use Event wrapper when there are more than one observer.
For more details:
https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150
Ín my app's room database I have a table called movie_table. When the user clicks the item, it is saved to another table called favorite_table. I tried almost two days but I can't solve it. I am new to MVVM.
For better understanding please see the code:
FavoriteMovieDao.kt
#Dao
interface FavoriteMovieDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertFavMovie(favorite: Favorite)
#Query("SELECT * FROM favorite_table WHERE id LIKE :id")
suspend fun getFavoriteMovieById(id:Int): Favorite
#Query("SELECT * FROM favorite_table")
suspend fun getAllFavoriteMovie(): List<Favorite>
#Delete
suspend fun deleteFavorite(favorite: Favorite)
}
Repository.kt
class Repository(context: Context) {
private val favoriteMovieDao: FavoriteMovieDao = MovieDatabase.invoke(context).getFavoriteMovieDao()
suspend fun insertFavMovie(favorite: Favorite){
favoriteMovieDao.insertFavMovie(favorite)
}
MovieDetailsViewModel.kt
class MovieDetailsViewModel(private val repository: Repository):ViewModel() {
private val favMovieResponse:MutableLiveData<List<Favorite>> = MutableLiveData()
val actorResponse:MutableLiveData<List<Movie>> = MutableLiveData()
private val insertFavMovie:MutableLiveData<Favorite> = MutableLiveData()
fun actorDetail(){
viewModelScope.launch {
val actor = repository.getAllMovieDB()
actorResponse.value = actor
}
}
fun insertFavMovie(favorite: Favorite){
viewModelScope.launch {
//i can't insert this
val insertFav = repository.insertFavMovie(favorite)
insertFavMovie.value = insertFav
}
}
}
}
MovieDetailsActivity.kt
class MovieDetailsActivity : AppCompatActivity(){
private lateinit var actorViewModel: MovieDeatilsViewModel
private val actorAdapter by lazy { ActorAdapter() }
private var isFav: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_movie_details)
val repository = Repository(this)
val viewModelFactory = MovieDetailsViewModelFactory(repository)
actorViewModel = ViewModelProvider(this,viewModelFactory).get(MovieDeatilsViewModel::class.java)
setUpPostRecyclerView()
actorViewModel.actorDetail()
actorViewModel.actorResponse.observe(this, Observer {actorList ->
actorAdapter.setData(actorList)
})
initBundle()
favBtn.setOnClickListener {
if (isFav){
isFav = false
favBtn.supportImageTintList = ColorStateList.valueOf(Color.parseColor("#E4C1C1"))
Log.d("msg","Not Favorite!")
}else{
isFav = true
favBtn.supportImageTintList = ColorStateList.valueOf(Color.parseColor("#FFC107"))
Log.d("msg","Favorite!")
}
}
}
private fun initBundle() {
val bundle:Bundle? = intent.extras
movieTitle.text = bundle!!.getString("title")
directorbc.text = bundle.getString("director")
genre.text = bundle.getString("genre")
releaseYear.text = bundle.getInt("year").toString()
language.text = bundle.getString("language")
country.text = bundle.getString("country")
rating.text = bundle.getString("rating")
plotText.text = bundle.getString("plot")
//moviePosterD.setImageDrawable(bundle!!.getString("image"))
Glide.with(this).load(bundle.getString("image")).into(moviePosterD);
val player = SimpleExoPlayer.Builder(this).build()
player.preparePlayer(movieView)
player.setSource(applicationContext, "http://html5videoformatconverter.com/data/images/happyfit2.mp4")
}
private fun setUpPostRecyclerView(){
actorRecyclerview.adapter = actorAdapter
actorRecyclerview.layoutManager = LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false)
}
}
How can I insert it? Please help me. Thank you
In Movies table, to handle the favorite movies, you can make use of a variable in each movie item which you can for example set to true or false. This allows you to query using filters to show them on the screen. I don't think you need another table to store Favourites. I hope that helps! I can help you with the design if you share more details there.
#Entity
data class Movie( val title: String, val isFavourite: Boolean = false)
Update 1:
When user clicks on favButton, in MovieViewModel update the Movie entity like this:
fun setMovie(movie: Movie){
_movie.value = movie
}
fun updateMovie(movie: Movie) {
movie.isFavourite = !movie.isFavourite
viewModelScope.launch {
repository.updateMovie(movie)
setMovie(movie)
}
}
in MovieDao
#Update
suspend fun updateMovie(movie: Movie)
Update 2:
In MoviesDao
#Update
suspend fun updateMovie(movie: Movie)
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertAll(vararg movie: Movie)
#Query("select * from movies_table where isFavourite = 1")
fun getFavMovies(): LiveData<List<Movie>>
#Query("Select * from movies_table")
fun getAllMovies(): LiveData<List<Movie>>
In Repository
val favMovies: LiveData<List<Movie>> = moviesDao.getFavMovies()
Update 3:
favBtn.setOnClickListener {
_viewModel.updateMovie(movie)
}
_viewModel.movie.observe(viewLifecycleOwner, Observer {
//Update UI based on isFavourite value on Movie
})
I have Room Entity Class "Symptom" with name of Symptom and id of it.
#Entity(tableName = "symptoms")
data class Symptom(
#PrimaryKey #NonNull val id: Int,
val name: String) {
override fun toString(): String {
return "Symptom $id: $name"
}
}
I'm getting it in the following classses:
SymptomDao
#Dao
interface SymptomDao {
#Query("SELECT * FROM symptoms WHERE id=:id LIMIT 1")
fun getSymptom(id: Int): Symptom
#Query("SELECT * FROM symptoms")
fun getAllSymptoms(): LiveData<List<Symptom>>
}
SymptomRepository
class SymptomRepository(private val symptomDao: SymptomDao) {
fun getSymptom(id: Int) = symptomDao.getSymptom(id)
fun getAllSymptoms() = symptomDao.getAllSymptoms()
}
SymptomsViewModel
class SymptomsViewModel(symptomRepository: SymptomRepository): ViewModel() {
private val symptomsList = symptomRepository.getAllSymptoms()
private val symptomsItemsList: MutableLiveData<List<SymptomItem>> = MutableLiveData()
fun getAllSymptoms(): LiveData<List<Symptom>> {
return symptomsList
}
fun getAllSymptomsItems(): LiveData<List<SymptomItem>> {
return symptomsItemsList
}
}
I have RecyclerView with list of SymptomItem with Checkboxes to remember which Symptoms of a list users chooses:
data class SymptomItem(
val symptom: Symptom,
var checked: Boolean = false)
Question
My question is how can I get LiveData<List<SymptomItem>> by LiveData<List<Symptom>>? I have just started learning MVVM and I can't find a simply answer how to do that. I have already tried to fill this list in various ways, but It loses checked variable every time I rotate my phone. I'll be grateful for any hints.
You'll need to store which items are checked by storing their Ids in a List within the ViewModel. Then you'll have combine the list of your Symptom objects and the list of which items are checked, and generate the list of SymptomItem objects.
I'm going to use Kotlin Flow to achieve this.
#Dao
interface SymptomDao {
#Query("SELECT * FROM symptoms")
fun flowAllSymptoms(): Flow<List<Symptom>>
}
class SymptomRepository(private val symptomDao: SymptomDao) {
fun flowAllSymptoms() = symptomDao.flowAllSymptoms()
}
class SymptomsViewModel(
private val symptomRepository: SymptomRepository
) : ViewModel() {
private val symptomsListFlow = symptomRepository.flowAllSymptoms()
private val symptomsItemsList: MutableLiveData<List<SymptomItem>> = MutableLiveData()
private var checkedIdsFlow = MutableStateFlow(emptyList<Int>())
init {
viewModelScope.launch {
collectSymptomsItems()
}
}
private suspend fun collectSymptomsItems() =
flowSymptomsItems().collect { symptomsItems ->
symptomsItemsList.postValue(symptomsItems)
}
private fun flowSymptomsItems() =
symptomsListFlow
.combine(checkedIdsFlow) { list, checkedIds ->
list.map { SymptomItem(it, checkedIds.contains(it.id)) }
}
fun checkItem(id: Int) {
(checkedIdsFlow.value as MutableList<Int>).add(id)
checkedIdsFlow.value = checkedIdsFlow.value
}
fun uncheckItem(id: Int) {
(checkedIdsFlow.value as MutableList<Int>).remove(id)
checkedIdsFlow.value = checkedIdsFlow.value
}
fun getSymptomsItems(): LiveData<List<SymptomItem>> {
return symptomsItemsList
}
}
In your Fragment, observe getSymptomsItems() and update your adapter data.
The code is not tested, you may have to make small adjustments to make it compile.
i want to use SearchView for search some element in room db and i have a problem with this because i cant use getFilter in RecyclerViewAdapter because i have ViewModel maybe whoes know how to combine all of this element in one project.
I search one way use Transormations.switchMap. But I couldn’t connect them.
ProductViewModel
class ProductViewModel(application: Application) : AndroidViewModel(application) {
private val repository: ProductRepository
val allProducts: LiveData<List<ProductEntity>>
private val searchStringLiveData = MutableLiveData<String>()
init {
val productDao = ProductsDB.getDatabase(application, viewModelScope).productDao()
repository = ProductRepository(productDao)
allProducts = repository.allProducts
searchStringLiveData.value = ""
}
fun insert(productEntity: ProductEntity) = viewModelScope.launch {
repository.insert(productEntity)
}
val products = Transformations.switchMap(searchStringLiveData) { string ->
repository.getAllListByName(string)
}
fun searchNameChanged(name: String) {
searchStringLiveData.value = name
}
}
ProductDao
interface ProductDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertProduct(productEntity: ProductEntity)
#Query("SELECT * from products")
fun getListAllProducts(): LiveData<List<ProductEntity>>
#Query("DELETE FROM products")
suspend fun deleteAll()
#Query("SELECT * FROM products where product_name_ent LIKE :name or LOWER(product_name_ent) like LOWER(:name)")
fun getListAllByName(name: String):LiveData<List<String>>
}
ProductDao
#Query("SELECT * FROM products where product_name_ent LIKE :name or LOWER(product_name_ent) like LOWER(:name)")
fun getListAllByName(name: String):LiveData<List<ProductEntity>>
This Method in your dao should return LiveData<List<ProductEntity>> and not LiveData<List<String>>, because this query selects everything (*) from the entity and not a specific column.
it is similar to (#Query("SELECT * from products") fun getListAllProducts():LiveData<List<ProductEntity>>)
ProductViewModel
class ProductViewModel(application: Application) : AndroidViewModel(application) {
private val repository: ProductRepository
init {
val productDao = ProductsDB.getDatabase(
application,
viewModelScope
).productDao()
repository = ProductRepository(productDao)
}
private val searchStringLiveData = MutableLiveData<String>("") //we can add initial value directly in the constructor
val allProducts: LiveData<List<ProductEntity>>=Transformations.switchMap(searchStringLiveData)
{
string->
if (TextUtils.isEmpty(string)) {
repository.allProducts()
} else {
repository.allProductsByName(string)
}
}
fun insert(productEntity: ProductEntity) = viewModelScope.launch {
repository.insert(productEntity)
}
fun searchNameChanged(name: String) {
searchStringLiveData.value = name
}
}
Repository
...with the other method that you have, add the following:
fun allProducts():LiveData<List<ProductEntity>>=productDao.getListAllProducts()
fun allProductsByNames(name:String):LiveData<List<ProductEntity>>=productDao.getListAllByName(name)
In Your Activity Or Fragment Where you have the recyclerview adapter
inside onCreate() (if it is an activity)
viewModel.allProducts.observe(this,Observer{products->
//populate your recyclerview here
})
or
onActivityCreated(if it is a fragment)
viewModel.allProducts.observe(viewLifecycleOwner,Observer{products->
//populate your recyclerview here
})
Now set a listener to the searchView, everytime the user submit the query , call viewModel.searchNameChanged(// pass the new value)
Hope this helps
Regards,
I'm using the PagedList architectural components using a Room database and I am having trouble returning results to the observe method.
Here is my Dao:
#Dao
interface WorkPackageStorageDao {
#Query("SELECT * from workpackages where id = :id")
fun getById(id: String): Workpackage
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(workpackage: Workpackage)
#Query("Select * from workpackages WHERE id LIKE '%' || :searchString || '%' order by :orderBy")
fun searchWorkpackages(searchString : String, orderBy : String) : DataSource.Factory<Int, Workpackage>
#Query("SELECT * FROM workpackages")
fun searchWorkPackgesTest() : List<Workpackage>
#Query("Select * from workpackages WHERE id LIKE '%' || :searchString || '%' order by :orderBy")
fun searchWorkPackgesTestQuery(searchString : String, orderBy : String) : List<Workpackage>
#Query("DELETE from workpackages")
fun deleteAll()
}
My repository:
fun getAllWorkPackagesTestQuery() : List<Workpackage> {
return workpackagesDao.searchWorkPackgesTestQuery("",SortedBy.WorkPackageNumber.type)
}
fun getAllWorkPackages() : DataSource.Factory<Int, Workpackage> {
return getSortedAndSearchedWorkPackages("",
SortedBy.WorkPackageNumber
)
}
fun getSortedAndSearchedWorkPackages(searchString : String, sortBy: SortedBy) : DataSource.Factory<Int, Workpackage> {
return workpackagesDao.searchWorkpackages(searchString,sortBy.type)
}
Here is the method in my view model:
suspend fun fetchWorkPackagesInitial(
workWeek: Configurations.AppWeek,
pagingLimit: Int
) {
coroutineScope {
withContext(Dispatchers.IO) {
val factory: DataSource.Factory<Int, Workpackage> =
workPackageRepository.getAllWorkPackages()
val pagedListBuilder =
LivePagedListBuilder<Int, Workpackage>(factory, pagingLimit)
workPackagesList = pagedListBuilder.build()
val list = workPackageRepository.getAllWorkPackagesTestQuery() //27 Items returned, query is fine.
}
}
}
Here is my fragment:
mainViewModel.week.observe(this, Observer {
it ?: return#Observer
launch { workPackagesViewModel.fetchWorkPackagesInitial(it, PAGING_LIMIT) }
})
//Observe never called.
workPackagesViewModel.workPackagesList?.observe(this, Observer { wpList ->
wpList ?: return#Observer
adapter = WorkPackagesRecyclerAdapter(this)
adapter.submitList(wpList)
binding.workPackagesRecyclerView.adapter = adapter
adapter.notifyDataSetChanged()
})
As a test to my query, I've implemented:
val list = workPackageRepository.getAllWorkPackagesTestQuery()
which returns 27 items, so query is fine. Am I setting up the the Dao wrong, the LivePagedListBuilder wrong? Why is observe not called?
You're not getting items because it's a PagedList. You need to trigger the load in order to obtain pages.
That is why giving the PagedList to a PagedListAdapter via submitList will eventually load the data.
You also don't need to manually invoke adapter.notifyDataSetChanged() when you're using a PagedListAdapter, because the DiffUtil will handle that internally.
However, you should definitely be retrieving the DataSource.Factory and the LivePagedListBuilder and the PagedList on the UI thread (Dispatcher.MAIN), because threading is handled by the Paging lib's Room integration internally. Observing (and invoking getItem( on an unloaded element) will trigger the load, and the load will be executed asynchronously by the DataSource out of the box.
The way to use Paging is like this:
class MyViewModel(
private val workPackageStorageDao: WorkPackageStorageDao
): ViewModel() {
private val searchQuery: MutableLiveData<String> = MutableLiveData("")
val workPackages: LiveData<PagedList<WorkPackage>> = Transformations.switchMap(searchQuery) { searchText ->
val factory = workPackageStorageDao.searchWorkPackages(searchText, SortedBy.WorkPackageNumber)
val pagedListBuilder = LivePagedListBuilder<Int, WorkPackage>(factory, pagingLimit)
pagedListBuilder.build()
}
}
Then in Fragment:
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ... setup RecyclerView, etc
viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java, viewModelFactory)
viewModel.workPackages.observe(viewLifecycleOwner, Observer { pagedList ->
adapter.submitList(pagedList)
})
}
And in adapter:
class MyAdapter: PagedListAdapter<WorkPackage, MyAdapter.ViewHolder>(WorkPackage.ITEM_CALLBACK) {
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val workPackage = getItem(position)
if(workPackage != null) {
holder.bind(workPackage)
}
}
}
Long story short, you don't need coroutines here. Use the Paging Library and LiveData, and it will load on the correct threads.