What kind of key would I use for this StoreBuilder - android

So I'm trying out Store 4 (https://github.com/dropbox/Store) with a simple android app that fetches data form this placeholder site: https://jsonplaceholder.typicode.com/
The first screen would be a list of all the users. I want to fetch them the first time you open the app. But when you reopen the app or get back to the first screen I want fetch it from the room database first and then fetch them from the internet (the fact that the database is not changing is trivial).
I expect my ViewModel would need to accept the object created by the StoreBuilder, so I'm creating the definition of this object in my DI (Koin). StoreBuilder keeps asking me to provide a Key though. And I'm beginning to suspect that Store is not the right choice if you're fetching all the users. I'm trying to use Store in my program because I'm interested in using it. My question is, how? Or simply put:
What is the key that I should use in this case? Should I use a key at all? Why do I need to use a key?
Small excerpt of my code so far (not that it builds):
object KoinModules {
val repositoryModule = module {
single { StoreBuilder
.fromNonFlow { provideUserService(get()).getAll() //** THIS IS NOT WORKING **
}.persister(
reader = provideUserDAO(get())::getAll,
writer = provideUserDAO(get())::insert
)
}
}
}
#Dao
interface UserDao {
#Query("SELECT * FROM UserDTO")
suspend fun getAll(): List<UserDto>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg userDTO: UserDto)
}
interface UserService {
#GET("/users")
suspend fun getAll(): List<User>
}
#Parcelize
data class User(val id: Int, val name: String, val username: String, val email: String) : Parcelable

this is the key describes:
Store uses generic keys as identifiers for data. A key can be any value object that properly implements toString(), equals() and hashCode(). When your Fetcher function is called, it will be passed a particular Key value. Similarly, the key will be used as a primary identifier within caches (Make sure to have a proper hashCode()!!).
the key is to define your data, the object which you use as key have to override the hashCode method, define your own rule.
here is some use in Store:
suspend fun Store.get(key: Key): Value: This method returns a single value for the given key. If available, it will be returned from the in memory cache or the persister
it is according to your set key to return the data. its functions as the key in hashmap

Related

Is room capable of getting data from dataclasses?

this might be a basic question but I cannot find any way to do it and I am not even sure whether that it is possible:
let's say I have a data class named "LoginCredentials", which takes 2 values: username and password (both strings)
could I make a query that looks like that?:
#Query("SELECT * FROM X WHERE username = :loginCredentials.username AND password = :loginCredentials.password")
or is there any other way to do it?
I don't think it's possible - I'm not a Room expert, but the documentation only mentions referencing parameters and expanding collections in a #Query string. There's nothing about using more complex, arbitary data structures.
I think the way you're meant to do it is with a public method in your DAO that takes your data structure, and have that internally call another function that takes the individual parameters:
#Dao
interface MyDao {
#Query("SELECT * FROM X WHERE username = :username AND password = :password")
fun getFromUserAndPass(username: String, password: String): Whatever
fun getFromLoginCreds(creds: LoginCredentials) =
getFromUserAndPass(creds.username, creds.password)
}
There's no way to make that "internal" version private that I know of (adding it to an interface disallows that, not sure if there's an alternative) but you can always abstract access to your DAO through a repository layer or something, with its own API

Database repository comparison and/or update in Android Kotlin?

I'm new to android programming and want to try to learn best practices. My first app I'm building is a podcast app to display podcasts from an rss feed and play them. What I have so far is working, but I know I can make it work better.
I'm using a Room Database with a Repository pattern, which might be overkill because I probably don't need to persist the podcast list across app death if I'm just going to re-parse the feed on startup. In my repository class I'm calling my FetchRSS class to do the network call in the init{ } block which returns a List<Podcast>.
I know I'm not doing something right.
In my PodcastDao, I have to use #Insert(onConflict = OnConflictStrategy.REPLACE) because the database already exists and I get an SQL error 1555 regarding duplicate primary key ids. Logically, it'd be better to have a check to see if the entry to be added is already in the database, but I'm not sure how to go about doing that. Or, illogically, clear the database on app death, but then why bother with a database at all. Ideally, I'd like to have a swipe to update function(even if the RSS only updates at most twice a week), but I'm not sure how best to do that.
If anyone has any thoughts about improving this, or a good book for learning android, I'd be all ears.
Thank you so much to everyone who takes the time to look at this!
PodcastDao.kt
#Dao
interface PodcastDao {
#Query("SELECT * FROM podcast") // get everything from the database
fun getPodcasts(): LiveData<List<Podcast>>
#Query("SELECT * FROM podcast WHERE id=(:id)") // get the specific podcast
fun getPodcast(id: String): LiveData<Podcast?>
// #Insert(onConflict = OnConflictStrategy.REPLACE)
// fun addPodcasts(podcasts: LiveData<List<Podcast>>)
// this causes a build error with the generated PodcastDao.java file
// logcat error: Type of the parameter must be a class annotated with #Entity or a collection/array of it.
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun addPodcast(podcast: Podcast)
}
PodcastRepository.kt
class PodcastRepository private constructor(context: Context) {
private lateinit var podcasts: List<Podcast>
init {
CoroutineScope(Dispatchers.Main).launch {
podcasts = FetchRSS().fetchRss() // executes on Dispatchers.IO and returns parsed rss List<Podcast>
// this seems silly to add them one at a time, especially since the list is rather large
for (pod in podcasts) {
addPodcast(pod)
}
//it seems a better choice to dump the full list into the database at once
//however I can't figure out how to put the List<Podcast> into a LiveData<List<Podcast>> object
//or maybe I'm misunderstanding something about LiveData<>
//addPodcasts(podcasts)
}
}
suspend fun addPodcast(podcast: Podcast){
withContext(Dispatchers.IO){
podcastDao.addPodcast(podcast)
}
// this needs to receive the LiveData<List<Podcast>>, or a List<Podcast> and cram it into LiveData<>?
// suspend fun addPodcasts(podcasts: LiveData<List<Podcast>>) {
// withContext(Dispatchers.IO){
// podcastDao.addPodcasts(podcasts)
// }
// }
}
fun addPodcasts(podcasts: LiveData<List<Podcast>>)
should be
fun addPodcasts(podcasts: <List<Podcast>>)
So, now you can call podcastDao.addPodcasts(podcasts) (where podcasts is of type List<Podcast>>) from inside your repository instead of inserting them one by one through a for loop.
You cannot insert a LiveData into Room, only objects marked with #Entity. You can, however, have a query return a LiveData with a List of those entities. You can also return just a List as well.

Room is there any way to not insert/update value if null

I've recentyly started using Room and I would like to know if when inserting or updating an Entitiy there's any way to check if the value is null, and in that case, do not insert it /update it.
What I want to do is something like
#Entity
data class Person{
#PrimaryKey
val id: Int,
val name:String
val surname:String
}
Would it be possible in a simple way to perform an #Update operation for those fields which are not null? and those which are null keep them as tey are?
For example in an update perhaps I might have informed the id and the name, but in another update I might have informed the id and the surname. So what I want is to merge the information, but if possible without having to make a select query to check the values stored.
I've read the following post, but my doubt then it would be, is it possible to define an #Entity, with all the fields defined as the one I mentioned before and then have other entities to just update some fields, something like:
#Entity(tableName = "person")
data class PersonUpdateSurname{
#PrimaryKey
val id: Int,
val name:String
}
#Entity(tableName = "person")
data class PersonUpdateSurname{
#PrimaryKey
val id: Int,
val surname:String
}
Is there a way to tell Room which is the original table structure?
Is there a way to tell Room which is the original table structure?
This question is not clear. Maybe there is some misunderstanding you have.
Try to follow next schema:
There should be only one Person-related data class annotated with Room-annotations - #Entity, #PrimaryKey and so on. In your case it is Person class.
All the rest mentioned auxiliary classes should be just POJO (plain data classes), since they are not being persisted. In your case - PersonName and PersoneSurname (with the fields you described but without Room's annotations).
In DAO use entity-parameter in #Update:
#Update(entity = Person::class)
fun updateName(personName: PersonName)
#Update(entity = Person::class)
fun updateSurname(personeSurname: PersonSurname)
In your Repository call method what you need. If you want to update only name - you use method updateName() and instance of PersonName class as a parameter, for only surname's update - method updateSurname() and instance of class PersonSurname.

Android Room library with Kotlin Flow toList() doesn't work

I made a simple example app with using Room and Flows:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val build = Room.databaseBuilder(this, FinanceDatabase::class.java, "database.db")
.fallbackToDestructiveMigration()
.build()
GlobalScope.launch {
build.currencyDao().addCurrency(CurrencyLocalEntity(1))
val toList = build.currencyDao().getAllCurrencies().toList()
Log.d("test", "list - $toList")
}
}
}
#Entity(tableName = "currency")
data class CurrencyLocalEntity(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "currencyId")
var id: Int
) {
constructor() : this(-1)
}
#Dao
interface CurrencyDao {
#Query("SELECT * FROM currency")
fun getAllCurrencies(): Flow<CurrencyLocalEntity>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addCurrency(currency: CurrencyLocalEntity)
}
#Database(entities = [CurrencyLocalEntity::class], version = 1)
abstract class FinanceDatabase : RoomDatabase() {
abstract fun currencyDao(): CurrencyDao
}
I want to use toList() function as in code above but something gets wrong and even Log doesn't print. At the same time using collect() works fine and gives me all records.
Can anybody explain to me what is wrong? Thanks.
There are a couple things wrong here but I'll address the main issue.
Flows returned by room emit the result of the query everytime the database is modified. (This might be scoped to table changes instead of the whole database).
Since the database can change at any point in the future, the Flow will (more or less) never complete because a change can always happen.
Your calling toList() on the returned Flow will suspend forever, since the Flow never completes. This conceptually makes sense since Room cannot give you the list of every change that will happen, without waiting for it to happen.
With this, I'm sure you know why collect gives you the records and toList() doesn't.
What you probably want here is this.
#Query("SELECT * FROM currency")
fun getAllCurrencies(): Flow<List<CurrencyLocalEntity>>
With this you can get the first result of the query with Flow<...>.first().
Flow in Room is for observing Changes in table.
Whenever any changes are made to the table, independent of which row is changed, the query will be re-triggered and the Flow will emit again.
However, this behavior of the database also means that if we update an unrelated row, our Flow will emit again, with the same result. Because SQLite database triggers only allow notifications at table level and not at row level, Room can’t know what exactly has changed in the table data
Make sure that the same doa object you are using for retrieving the list, is used for updating the database.
other than that converting flow to livedata is done using asLivedata extension function
For me below solution works for updating the view with database table changes.
Solution: Same Dao Object should be used when we insert details into the room database and get information from DB.
If you are using a dagger hilt then
#Singleton annotation will work.
I hope this will solve your problem.
**getAllCurrencies()** function should be suspend.
Please check the syntax to collect List from Flow:
suspend fun <T> Flow<T>.toList(
destination: MutableList<T> = ArrayList()
): List<T> (source)
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-list.html

Type Consistency for Retrofit, RxJava2, and Room: How to deserialise JSON with same payload, but different 'root key' if response is Array or Object

I'm struggling to maintain consistent Types in my Kotlin application across Retrofit, Room, and RxJava2 due to the JSON key naming convention that an API uses. I've attempted to research the best way to tackle the problem below, but I can't think of a better way to solve it, and I'm interested in what the community has to offer.
Consider the following example;
I'm using the Kotlin language on Android, and performing network requests with Retrofit and GSON. The API I'm using provides JSON with a different root key name depending on whether the response is an Array or a single Object.
The payload of each Object is the same, but if the response is an Array, the root key is plural.
For example, let's say I have a Data Class defined for a user:
data class User(var id: Int?, name: String?)
If I make an API request for a single user, the API returns the following:
{
"user": {
"id": 1,
"name: "Sam"
}
}
If I make an API request for multiple users, the API returns the following:
{
"users": [{
"id": 1,
"name: "Sam"
}]
}
The payload is the same, but the root key changes between user and users. I've created a Data Class to handle the separate root keys:
data class Users(
#SerializedName("users") var users: List<User>?,
#SerializedName("user") var user: User?
)
And when making the separate API requests, I use the Users Data Class for both:
/** GET single user by ID */
#GET("users/{id}")
fun getUser(
#Path("id") id: Int?): Single<Users>
/** GET list of users */
#GET("users")
fun getUsers(): Single<Users>
And when I save the response to the Room database, I access it using response.users or response.user respectively. Room is set up to return Queries as either User or List<User>, and this creates a problem when using the Retrofit requests in an RxJava2 stream. For example, this is a very simple stream if the Room database returns Maybe<List<User>>:
usersRepository.getAll()
.switchIfEmpty(api.getUsers())
The result of usersRepository.getAll() is List<User>, but if the result from the database is empty and I attempt to fallback on an API request instead, RxJava2 shows an Incompatible Type error because the api.getUsers() request returns the Type Single<Users> rather than Single<List<User>>. If the API request was able to use the same type, I wouldn't have a problem, but I'm not able to specify Single<List<User>> on the Retrofit request because GSON can't deserialise the response correctly.
Because of this, from my current understanding of the issue (which could be layman and I'm open to being moulded), I'm separating the database and API requests, and doing a lot of empty/null checking, which prevents me from keeping everything in one nice and tidy RxJava2 stream.
I hope this question is clear enough, and I value anyone's input. Thank you for your time!
This is a very common problem and what people like to do is to create a wrapper class that deals with conversions, validations, and all other painful jobs, and use that class to do network requests instead of calling the retrofit api directly.
For example, consider creating a class like this:
class UserClient {
private val userApi: UserApi
fun getUser(id: Int): Single<User> {
return userApi.getUser(id) // This has a type of Single<Users>
.map { it.user } // now it's map to Single<User>
}
fun getUsers(): Single<List<User>> {
return userApi.getUsers() // This has a type of Single<Users>
.doOnSuccess { Log.d(TAG, "yay!")}
.doOnError { e -> Log.d(TAG, "Something went wrong: $e")}
.map { it.users } // Now it's map to Single<List<User>>
}
}
Then you can do something like this:
usersRepository.getAll()
.switchIfEmpty(UserClient.getInstance().getUsers())

Categories

Resources