How to retrieve data cached using Room - android

I have an app where I fetch posts on my main list and store them in Room, more specifically, in a Post table.
To get the posts from my main list I can't get all the posts from my Post table as there are other points in the application from which posts are fetched and stored, such as a post search listing.
I would like to know how to handle my problem, I am open to hearing any opinions.

I think you can mark posts fetched for main list.
Suppose the post model:
#Entity(tableName = "`YOUR_TABLE`")
internal data class PostEntity(
#PrimaryKey val id: Long,
#ColumnInfo(name = "is_main")
val isMain: Boolean, // true means that post was fetched for main list
...
)
And you should write the query request to the Room.
For example:
#Query(
"SELECT * " +
"FROM `YOUR_TABLE` " +
"WHERE post.is_main = 1"
)
suspend fun getMainListPosts(): List<PostEntity>

Related

Changing primaryKeys order changes the order of the data saved in ROOM

I have a situation where I need some information about my results, in my code I have this data class/Room's entity.
#Entity(primaryKeys = ["searchId","page","pr_id"])
data class ProductResponse(
var searchId: Int,
val page: Int,
#Embedded(prefix = "pr_")
val products: ProductSearchFormatted
)
Where you can see in the Android Studios App Inspector:
The data is saved as spected in ROOM, when I try to load it from ROOM:
#Query(
"SELECT * FROM PagedSearchResponse WHERE searchId ==:input"
)
fun loadPagedSearchResponse(input: Int): PagingSource<Int, ProductResponse>
I just need the data in the same order that was previously saved, and got the data in different order (ordered by pr_id):
I found out that if I change the primaryKeys order, like
#Entity(primaryKeys = ["pr_id","searchId","page"])
data class ProductResponse(
var searchId: Int,
val page: Int,
#Embedded(prefix = "pr_")
val products: ProductSearchFormatted
)
Now the data's order from ROOM is correct.
Why does this happen? Does the primaryKeys order matter?
Changing primaryKeys order changes the order of the data saved in ROOM
NO it does not, the data is ALWAYS saved in the order in which it is inserted. What is changing in your case, is the ORDER in which the data is extracted. That is because you aren't saying in what ORDER you want the data to be extracted and are leaving that choice to the query planner.
Why is this happened?, does the primaryKeys order matter?.
Yes it can do, especially in the absence of other indexes. Certainly pr_id before search_id will make a difference as the order within the index will be different and that as the WHERE clause is on the search_id then it is likely that the primary key index will be used (as in both cases search_id is an initial column (see the links below))
The query planner is an AI that tries to pick the fastest and most efficient algorithm for each SQL statement.
see :-
https://www.sqlite.org/queryplanner.html
https://www.sqlite.org/optoverview.html
https://www.sqlite.org/queryplanner-ng.html
If you want data to be in ORDER then you should specify an ORDER clause (ORDER BY ....). That is the only way to guarantee an ORDER. Assuming an ORDER without an ORDER clause will very likely result in issues.
Saying that using pr_id prior to search_id makes the composite (multiplte column) index likely to be more beneficial as the pr_id (according to the data shown) is less repeated than the search_id.
The way I fix it, it was simpler than I expected.
In the entity, I added a field called "order", and in my Mediator (I'm using Paging 3) basically did this:
list.mapIndex { s,t ->
ProductResponse(
...
order = s
...
)
}
That way, I'm following the EXACT order from Backend without the need to modify any primaryKeys.

Android room, map to existing model

I have a room database that stores a list of vacation resorts, which includes fields for State and Country, and am trying to get a distinct list of the State/Country combinations.
So far I have:
#Query("SELECT DISTINCT state,country from ResortdatabaseModel")
List<StateModel> getAllStates();
However, StateModel is not an entity within the database, it's a model defined elsewhere in the project, and I'm getting an error "Not sure how to convert a Cursor to this method's return type "
How do I map the query results to the exiting model?
The class StateModel needs to have the properties state and country with the same types as those two columns in Resortdatabasemodel, e.g.:
data class StateModel(
val state: String,
val country: String,
)
(String is just an example here, I don't know the actual types in your project)

How to insert a duplicate row in Room Db?

While searching for this, I only came across people asking how to Avoid inserting duplicate rows using room db. But my app has a feature where the user may tap a copy button and the list item will get inserted again in the db. I could have simply achieved this if my table didn't have a primary key set on one of its fields. While I found this solution for SQLite, I don't know how I can achieve this in Room Db. Because while writing an insert query with custom queries in room would defeat the purpose of using room in the first place.
Let's say you have some entity
#Entity(tableName = "foo_table")
data class Foo (
#PrimaryKey(autoGenerate = true) var id: Int,
// or without autogeneration
// #PrimaryKey var id: Int = 0,
var bar:String
)
and you have some Dao with insert:
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(foo: Foo)
Then to copy your existing value (copiedValue: Foo) you need in some way to manage your primary key:
Scenario 1. Your Primary Key is autogenerated, you have to set it to default value to get new autogenerated one:
copiedValue.id = 0
yourDao.insert(copiedValue)
Scenario 2. Your Primary Key is not autogenerated, you have to set new primary key manually:
copiedValue.id = ... // some code to set new unique id
yourDao.insert(copiedValue)

Best practice when interacting with Room and SharedPreferences

I'm using a Room table that takes an entity that has a "Favorited" boolean field, which is by default False, but can be made True through user input. The database needs to be wiped periodically and repopulated by a network call. Doing so naturally reverts all "Favorited" fields to False.
Using SharedPreferences, I can keep a second, persistent list of Favorited entities, but what's the cleanest way to repopulate Room and hold on to the favorites?
Using RxKotlin here for clarity, if not accuracy,
should it be something like
saveEntities()
.flatMapIterable { it }
.map{
if (sharedPrefs.contains(it.id))
it.apply{favorite}
else it }
.toList()
.subscribe()
Or should I serialize a list of favorites, save that to SharedPreferences, and then
val favoritesList = PrefSerializer().getFavorites
saveEntities()
.map{ it.forEach{
if (favoritesList.contains(it))
it.apply{favorite}
else it}
}
.subscribe()
Or does it even make sense to store Favorite information in Room?
Referencing SharedPreferences every time an entity is called feels like bad practice.
I would recommend using 2 tables in Room because that way you can use a database view to combine them. Room can monitor the tables and notify the UI via LiveData. You can technically monitor SharedPreferences, but I believe it is in fact more tedious.
For example, this is a table where data is periodically overwritten by sync.
#Entity
data class Cheese(
#PrimaryKey
val id: Long,
val name: String
)
And you have a separate table to hold the "favorite" status.
#Entity
data class CheeseFavorite(
#PrimaryKey
val id: Long,
val favorite: Boolean
)
Then you can create a database view like this to combine 2 tables.
#DatabaseView(
"""
SELECT c.id, c.name, f.favorite
FROM Cheese AS c
LEFT OUTER JOIN CheeseFavorite AS f
ON c.id = f.id
"""
)
data class CheeseDetail(
val id: Long,
val name: String,
val favorite: Boolean?
)
The view can be queried just like a table. You can of course use LiveData or Rx data types as the return type.
#Query("SELECT * FROM CheeseDetail")
fun all(): List<CheeseDetail>

Data source always returns full data from room data base even if I provided the amount of data loaded each page

I am using paging (part of android Jetpack ) to retrieve data from my room DB. I wanted to fetch 50 records on each page. But 'DataSource.Factory.toLivedata(pageSize)' function returning full data from the database.
I have tried providing configuration for toLivedata() method but seems like its ignored
private val config = PagedList.Config.Builder()
.setInitialLoadSizeHint(50)
.setPageSize(50)
.build()
fun getPagedData(): LiveData<PagedList<TransactionEntity>> =
transactionDao.getAllTransactions().toLiveData(config)
//TransctionDao
#Query("SELECT * FROM transaction_table order by date DESC")
fun getAllTransactions(): DataSource.Factory<Int, TransactionEntity>
//Observing Live Data
getPagedData().observe(this,Observer<PagedList<TransactionEntity>> { t ->
Log.d("Activity", "Size is " + t.size)
})
I was expecting the size of PagedList as 50, but its always giving entire records from transaction_table
The reason you think all the data is loaded is that PagedList uses placeholders for data that was not yet loaded and so as in the documentation the size represents the whole data size and not just the loaded data size.
If you want to change this you can use setEnablePlaceholders(false) in the config builder. You can test it by calling get(6500) and you should get null as this item was not loaded yet.
I advise you to read the PlaceHolders section.

Categories

Resources