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)
Related
That is my current dao
#PrimaryKey(autoGenerate = true)
val id: Int,
val name: String,
val date: LocalDate,
val amount: Int,
val uri: String,
val tag: String,
val toList: Boolean,
val inUse: Boolean,
val listValue: Int
now I have the problem that in a previous version of that dao there is a variable in that table that I now want to remove.
I found a 4 step guid:
1.) create new table 2.) insert from the old table 3.) drop the old table 4.) alter new table name back to old table name
that's fine but my problem is that I have a variable with a LocalDate which uses a DateTypeConverter to function properly.
How do I insert that LocalDate into the new table? I just know of TEXT and INTEGER
Step 2 use the SupportSQliteDatabase's execSQL method to execeute a query based upon the SQL
INSERT INTO <the_table> SELECT <the_columns> FROM <the_old_table>;
Where:-
anything enclosed within <> needs to be altered accordingly as per:-
<the_table> should be replaced with the new table name.
<the_columns> should be replaced with the column names, separated by commas, LESS THE DROPPED COLUMN NAME
<the_old_table> should be replaced with the old/original table name.
Note that a variable name will be the same as the variable name.
The above will copy the values, whatever they are, as stored in the database, from the old to the new table.
The TypeConverters are only used to convert the data to or from the respective object (LocalDate in your case) when storing or retrieving the stored data.
A type converter should consist of two functions:-
1 to convert the object to a type that can be stored in an SQLite database (SQLite is a universal database that has no concept of a programming languages objects). The SQLite types being
INTEGER (not necessarily a Kotlin Int, could be a Long, Byte even a Boolean ....).
TEXT (a Kotlin String ....)
REAL (Kolin Double, Float ....)
BLOB (Kotlin ByteArray ....)
NULL
2 to convert the stored type into the object when retrieving data from the database. As such it is no issue at all for the INSERT INTO table SELECT ....; to copy the existing data from one table to another irrespective of Room's handling of the data when it stores and retrieves the data.
The result being that the data is stored in the database as either one of the 5 types. As such it is no is
If the "current dao" (it is not a dao, it is an entity that should be annotated with #Entity, which equates to a table) is after the removal of the dropped variable then you would use:-
INSERT INTO <the_new_table> SELECT id,name,date,amount,uri,tag,toList,inUse,listValue FROM <the_old_table>;
You may wish to refer to 2. INSERT INTO table SELECT ...;
If you want to add data into date column of old table data.
fun updateData() {
val list = dao.getAllData()
list.forEach {
//update data
}
dao.saveData(list)
}
I want my table names to be specific as I am migrating from Native Android Room database to Flutter using Drift library for database.
Room take a table name i.e. class name and creates a table:
e.g. the following will create table 'User' with field names as 'firstName' and lastName' in native Android using Room database support
#Entity
data class User(
#PrimaryKey val id: Int,
val firstName: String?,
val lastName: String?
)
When I try to replicate this in Flutter, I write:
#DataClassName('User') // To avoid drift changing it to 'user'
data class User(
TextColumn get firstName => text().named('firstName')();
TextColumn get lastName => text().named('lastName')();
// The above '.named()' makes sure drift does not change names to 'first_name' & 'last_name'
)
By doing above I am making sure the databases match exactly! (please correct me if I am wrong here)
Now when I try to replicate the migration statements in drift I am writing:
...
if (from < 4) await m.addColumn(Note, Note.path);
if (from < 5) await m.addColumn(Profile, Profile.squarePicture);
...
As you can see that I am writing note as a 'Note' because I have defined my table name as 'Note' and not 'note' same goes with profile! But the code is highlighting the following error:
The argument type 'Type' can't be assigned to the parameter type
'TableInfo<Table, dynamic>'
When I change table names to small alphabets i.e. 'n' and 'p' the error goes away but I do not understand WHY? My table names are explicitly with capital letters in the start.
I do not want to change anything in the database when my users will upgrade their app which was previously made in Native Android using Kotlin and Room database to the Flutter version. I want them to have a seamless experience with no loss of data!
The #DataClassName() annotation is used to change the name of the class which is generated by the drift. This generated class is what the drift will use when querying the database and getting the result.
By default, the generated class name will be the same as the class name except the drift will strip any last 's' character. For example, if your class name is "Users" the drift will generate a class with the name "User" with the 's' at the end stripped.
Coming to your question #DataClassName() has nothing to do with the table name in your database or any migration strategy. If you want to change the table name you can do something like this,
class User extends Table {
///This property will change the table name.
String get tableName => 'User';
...
}
Official Documentation - Dart table Names
Drift also generates a class that has an identical name to our table class. In your case, there will be a class generated $User which extends our table class, and a mixin TableInfo. This class's object is automatically created by the drift in the database generated code.
So drift will generate a variable named user which will be object of class $User. This variable needs to be used inside migration stregeies so that drift can successfully migrate a table.
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.
I have a room database setup and I want to query that database N number of times and combine the results of each query into a live data array to display back to the user.
I'm pretty sure I want to be using MediatorLiveData but every example online has a predefined amount of live data sources it is combining.
I have the following setup:
petDao
#Query("SELECT * FROM pet_table WHERE name LIKE :petName")
fun getPetsByPetName(petName: String): LiveData<Pet>
petRepository
fun getPetsByPetName(petNames: List<String>): LiveData<List<Pet>> {
for (petName: String in petNames) {
val pets = petDao.getPetsByPetName(petName)
// Combine into one live list of pets
}
}
Have you tried this in your DAO?
#Query("SELECT * FROM pet_table WHERE name IN (:petNames)")
fun getPetsByPetName(petNames: List<String>): LiveData<List<Pet>>
It should work with a list of up to 999 arguments. (not sure if the parameter has to be an array, or if the list is fine)
As an extension over SQLite bind arguments, Room supports binding a
list of parameters to the query. At runtime, Room will build the
correct query to have matching number of bind arguments depending on
the number of items in the method parameter.
https://developer.android.com/reference/androidx/room/Query
To me it seems more appropriate for the example you've given.
#Entity(tableName="user_table")
data class User(
#PriamryKey(autoGenerate = false) val id:Int,
#Embeded(prefix="address_") val address:Address
)
#Entity(tableName = "address_table")
data class Address(
#PrimaryKey(autoGenerate = false) val id:Int
)
Is there a way to just ignore the Id column from Address table because from my knowledge I will be getting
columns id, address_id in the user object once created
I have similar columns here and there and some are no longer in use once I create views for these tables i.e foreign keys etc
Is there a way to just ignore the Id column from Address.
I don't believe so. Assuming that Address has other fields (otherwise simply don't Embed) then to Embed an Entity with an Entity is de-normalising the database.
If you have decided that you don't need to store the addresses in a seperate table as there is effectively a 1-1 relationship then you should do away with the address table, which could/would be converting the Address class to not be an Entity and remove the id field.
If you were to keep the Address class as an Entity then you cannot #Ignore the #PrimaryKey as Room requires that an Entity has an #PrimaryKey. Really, in this situation of having another table for the address, you should reference/map the address to not de-normalise and thus just have a field in the user_table for that reference/mapping rather than Embed. You would/could then have a non-entity class (POJO) for extracting data that would use Embed both Entities or Embed one and Relate the other.