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
Related
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.
I wanna duplicate realm object and then change the second, without reassigning all the keys. How can I do this? RealmObject does not have .copy() or .clone() methods.
// Money is not data class and has ∞ fields which will be boring to re-assign
val money = Money()
money.amount = 1000
...
val anotherMoney = money
anotherMoney.amount = 500
println(money.amount) // prints 500
can you please provide more context and appropriate information as I do not see and array's in your code statement. Thank you.
EDIT
Because Money is not a data class, you do not have the auto-generated copy() function available, that leaves you with two options:
Create an custom copy() function in the Money class. This could be mundane if there are huge amount of fields in the class.
Use 3rd party libraries, in which case you'll add external dependency to your RealmObject.
What I will suggest is a no brainer: Try to convert your Money.class to a Data class. You will get auto generated functions and idiomatically it will work as RealmObjects should be key-values pairs.
EDIT
You can use GSON library's serialization/deserialization and hack your way to solve your problem (although this is a hacky way but will do its job):
fun clone(): Money {
val stringMoney = Gson().toJson(this, Money::class.java)
return Gson().fromJson<Money>(stringMoney, Money::class.java)
}
usage:
val originalMoney = Money()
val moneyClone = originalMoney.clone()
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
I'm querying Realm using the snippet below where I'm providing hardcoded fieldName which I believe is quite error-prone. I've checked realm documentation (to no avail) but in the official example (https://github.com/realm/realm-java/blob/master/examples/newsreaderExample/src/main/java/io/realm/examples/newsreader/model/entity/NYTimesStory.java), they're using static fields to access from queries. Which, again, I think is quite "dangerous" considering future field name changes and etc. So, what I wonder is whether there is a solution that I'm missing out?
Real example:
override fun getSection(section: Section): RealmResults<Article> {
return realm.where(Article::class.java)
.equalTo("section",
,section.section).findAllAsync()
}
What I'm trying to achieve:
override fun getSection(section: Section): RealmResults<Article> {
return realm.where(Article::class.java)
.equalTo(Article.SECTION, // or ArticleX.SECTION, X being name extension
,section.section).findAllAsync()
}
I was just wondering is there any convention about room queries return type? It is better to return with List or MutableList?
It's very easy to convert in Kotlin by .toList() and .toMutableList(). I'm just want to create an eye handy code that is why I'm asking about the best practice.
To See clear, I'm talking about these queries:
#Query("SELECT * FROM measured_attribute WHERE deliveryStatus = :status LIMIT 1000")
fun getAttributeEntityListByStatus(status: DeliveryStatus): List<MeasuredAttributeEntity>
#Query("SELECT * FROM measured_attribute WHERE
name = :speific AND
fk_patient = :id AND
creationTime>:beginTime ORDER BY creationTime")
fun getSpecificAttributeEntity(
speific: String,
id: Long,
beginTime: Long
): MutableList<MeasuredAttributeEntity>
Room will create a java.util.ArrayList for the results (you can check this by jumping into the implementation of your Dao after building your project), so you can use either return type in this case, as an ArrayList implements both interfaces. In general, the best practice in Kotlin is to use a read-only List as long as you can get away with it, so I'd suggest going with that.