I have designed a room db for my android app. All of my DAO interface methods are working except when I have a min/max functions in a query.
Here is mock of entity class:
#Entity
public class Data {
#PrimaryKey
#NonNull
private String timestamp;
#ColumnInfo(name = "item_key")
private int itemKey;
...Other stuff...
}
and here is a class to have only the needed parts:
public class HeaderData {
private int item_key;
private String timestamp;
public HeaderData(int item_key, String timestamp) {
this.item_key = item_key;
this.timestamp = timestamp;
}
}
and here is DAO interface:
#Dao
public interface LocationDao {
#Insert
void insertSingleRecord(Data data);
#Query("SELECT * FROM Data")
List<LocationData> getEverything();
#Query("SELECT * FROM Data WHERE item_key = :itemKey")
List<Data> getDbData(int itemKey);
// I dont know why this query is not working!
#Query("SELECT item_key, MIN(timestamp) FROM LocationData GROUP BY item_key")
List<HeaderData> getHeaderData();
}
In the last query getHeaderData() the MIN function seems not working and I get a list of header data with correct item_keys but all the timestamps are null . Does anybody knows the problem?
THe problem is that your return pojo is not exactly what you're asking from the database:
// I dont know why this query is not working!
#Query("SELECT item_key, MIN(timestamp) FROM LocationData GROUP BY item_key")
List<HeaderData> getHeaderData();
HeaderData does have an item_key, but MIN(timestamp) is not the same as timestamp, it's another column. That's why timestamp is comming as null. What you can do is define your pojo like this:
public class HeaderData {
private int item_key;
private String minTimestamp;
public HeaderData(int item_key, String minTimestamp) {
this.item_key = item_key;
this.minTimestamp = minTimestamp;
}
}
And redefine your query as such:
#Query("SELECT item_key, MIN(timestamp) as minTimestamp FROM LocationData GROUP BY item_key")
List<HeaderData> getHeaderData();
Related
I am implementing Room Database. Here is my POJO Class
public class Task implements Serializable {
#PrimaryKey(autoGenerate = true)
private int id;
#ColumnInfo(name = "task_name")
private String task;
#ColumnInfo(name = "description")
private String desc;
#ColumnInfo(name = "finish_by")
private String finishBy;
#ColumnInfo(name = "finished")
private boolean finished;
#ColumnInfo(name="no_of_days")
private String no_of_days;
public String getNo_of_days() {
return no_of_days;
}
public void setNo_of_days(String no_of_days) {
this.no_of_days = no_of_days;
}
}
This is the DAO Class
#Dao
public interface TaskDao {
#Update
void update(Task task);
#Query("SELECT task_name,description,no_of_days FROM task")
List<Task> getTasksandDescription();
}
While running my code, I am getting the following error
com.example.myapplication1.model.Task has some fields [id, finish_by, finished] which are not returned by the query. If they are not supposed to be read from the result, you can mark them with #Ignore annotation. You can suppress this warning by annotating the method with #SuppressWarnings(RoomWarnings.CURSOR_MISMATCH).
Columns returned by the query: task_name, description, no_of_days. Fields in com.example.myapplication1.model.Task: id, task_name, description, finish_by, finished, no_of_days.
This is not an error but a warning, it's telling you that you are mapping an SQL to a Pojo but you are not returning all the fields required to set up the Pojo. So your class has more fields than is returned from the query, you can fix this by doing the following.
I. Do a select * to return all the fields
#Query("SELECT * FROM task")
II. Add #Ignore annotation to the fields you're not interested in
III. Create another Java class that contains only the fields you're interested in and return in from the query instead.
Using the #Relation annotation. I can query a one to many relationship using the following:
#Dao
public interface PostDao {
#Query("SELECT * FROM post")
List<PostWithComments> getPostWithComments();
}
Here are the entities
#Entity
public class Post {
#PrimrayKey
private int id;
private String title;
private String content;
}
#Entity
public class Comment {
#PrimrayKey
private int id;
private int post_id;
private String content;
private String status;
}
public class PostWithComments {
#Embedded
public Post post;
#Relation(parentColumn = "id", entityColumn = "post_id", entity = Comment.class)
public List<Comment> comments;
}
I would like to get all posts that have a comment with status = approved but I'm not exactly sure how room handles this. I tried the following:
#Dao
public interface PostDao {
#Query("SELECT * FROM post INNER JOIN comment ON post.id = comment.post_id WHERE comment.status = 'approved'")
List<PostWithComments> getPostWithComments();
}
I got duplicates in the results. Each post is there multiple times in List<PostWithComments> results.
Update:
After reading the generated code at PostDao_Impl.java it seems that Room is doing a sub query to fetch the relation.
First, it executes the query in the #Query annotation from the getPostWithComments method, and then it generates a sub query for the relation to populate List<Comment>
SELECT id, post_id, title, content FROM comment WHERE post_id IN ( and some other logic, and there doesn't seem to be a way to modify the generated sub query.
Is there another way to do this?
With #Relation, you can use #DatabaseView
#DatabaseView("SELECT * FROM comments WHERE status = 'approved'")
public class ApprovedComment {
#Embedded
Comment comment;
}
PostWithComments class
public class PostWithComments {
#Embedded
public Post post;
#Relation(parentColumn = "id", entityColumn = "post_id", entity = ApprovedComment.class)
public List<ApprovedComment> comments;
}
DAO
#Dao
public interface PostWithCommentsDao {
#Query("SELECT * FROM post")
List<PostWithComments> getPostWithComments();
}
You also need to update your Database class that extends RoomDatabase and you may need to update the version.
#Database(entities = {Post.class, Comment.class}, views = {ApprovedComment.class}, version = 1)
public abstract class MyDatabase extends RoomDatabase
Not tested but you can try this...
public class PostWithComments {
#Embedded
public Post post;
#Embedded
public Comment comment;
}
#Dao
public interface PostWithCommentsDao {
#Query("SELECT post.*, comment.* FROM post LEFT JOIN comment ON post.id=comment.post_id where comment.status = 'approved'")
List<PostWithComments> getPostWithComments();
}
I'm trying to store values of some variable that my application regulary obtains from API. I whant to add new row to the database table only when variable changes its value to be able to show user some kind of "history of changes". I'm using ROOM for storing data.
I've created an entity:
#Entity(tableName = "balance_history",
indices = {#Index("received_at")})
public class BalanceResponse {
//region getters & setters
...
//endregion
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
private long mId;
#ColumnInfo(name = "money")
private double mMoney;
#ColumnInfo(name = "received_at")
private DateTime mReceivedAt;
}
Dao:
#Dao
public abstract class DatabaseDao {
#Query("SELECT * FROM balance_history ORDER BY received_at DESC LIMIT 1")
public abstract LiveData<BalanceResponse> selectLatestBalanceResponse();
public void insertNewBalanceResponse(BalanceResponse balanceResponse) {
String sqlRequest = "INSERT INTO balance_history(money, received_at) " +
"SELECT ?, ? " +
"WHERE NOT EXISTS(SELECT 1 FROM (SELECT * FROM balance_history ORDER BY received_at DESC LIMIT 1) WHERE money = ?);";
SupportSQLiteDatabase database = DatabaseStorage.getInstance().getAppDatabase().getOpenHelper().getWritableDatabase();
database.execSQL(sqlRequest,
new Object[]{balanceResponse.getMoney(), balanceResponse.getReceivedAt().getMillis(), balanceResponse.getMoney()});
}
}
Database object:
#Database(entities = {BalanceResponse.class}, version = 1)
#TypeConverters(DateTimeConverter.class)
public abstract class AppDatabase
extends RoomDatabase {
public abstract DatabaseDao getDatabseDao();
}
Singleton for storing single database object:
public class DatabaseStorage {
//region singleton
private static final DatabaseStorage ourInstance = new DatabaseStorage();
public static DatabaseStorage getInstance() {
return ourInstance;
}
//endregion
#NonNull
public AppDatabase getAppDatabase() {
return mAppDatabase;
}
#NonNull
private AppDatabase mAppDatabase;
private DatabaseStorage() {
mAppDatabase =
Room.databaseBuilder(MyApp.getAppContext(), AppDatabase.class, "app-database")
.build();
}
}
And viewmodel that I instantiate in my Activity's onCreate():
public class BalanceView implements Observer<BalanceResponse> {
private LiveData<BalanceResponse> mLatestBalanceResponse;
public BalanceView(LifecycleOwner lifecycleOwner){
mLatestBalanceResponse = DatabaseStorage.getInstance().getAppDatabase().getDatabseDao()
.selectLatestBalanceResponse();
mLatestBalanceResponse.observe(lifecycleOwner, this);
//finding views here
}
#Override
public void onChanged(#Nullable BalanceResponse balanceResponse) {
//displaying changes here
}
}
I've expected triggering of BalanceView.onChanges() method each time when method DatabaseDao.insertNewBalanceResponse() inserts a row.
Actually BalanceView.onChanges() method never gets fired. Why is that so? How can I accomplish this?
p.s. However, If I replace method DatabaseDao.insertNewBalanceResponse() with original:
#Insert
public abstract Long insertBalanceResponse(BalanceResponse balanceResponse);
Everithing works fine and method onChange() gets invoked. But this kind of insert statement doesn't fit my needs.
I have the same issue and here I got a hint to solve this issue.
#Dao
interface RawDao {
#RawQuery
User getUserViaQuery(SupportSQLiteQuery query);
}
SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT * FROM User WHERE id = ? LIMIT 1",
new Object[]{userId});
User user2 = rawDao.getUserViaQuery(query);
For More details, check https://developer.android.com/reference/android/arch/persistence/room/RawQuery.
I am having a hard time getting a list item into room. the list item is called measurements and its of type Measurement. the list item has no primarykey that would be related to the database.
but i have no problem adding the same primary key for the ProductModel if necessary.
Here is what i have so far:
#Entity(tableName = TABLE_NAME)
public class ProductModel {
public static final String TABLE_NAME = "product";
#PrimaryKey
private int idProduct;
private int idCategoryDefault;
#Relation(parentColumn = "idProduct", entityColumn = "idProduct", entity = SortedAttribute.class)
private List<SortedAttribute> sortedAttributes = null;
}
#Entity
public class SortedAttribute {
#PrimaryKey
private int idProduct;
private String reference;
#Embedded
private List<Measurement> measurements = null; //****how do i get this into room ? its a LIST of measurements, not a measurement so calling Embedded i think wont work as it cant flatten it****/
}
public class Measurement {
private String value;
private String valueCm;
public Measurement() {
}
}
Embedded annotation can be used on a POJO or Entity only, not for a List. So, Room can not automatically flatten your list in this case.
You can use TypeConverter to convert List<Measurement> into String(in JSON format) and vise versa. You can use any JSON parser library to support it. For example, I use Gson as following.
public class ProductTypeConverters {
#TypeConverter
public static List<Measurement> stringToMeasurements(String json) {
Gson gson = new Gson();
Type type = new TypeToken<List<Measurement>>() {}.getType();
List<Measurement> measurements = gson.fromJson(json, type);
return measurements;
}
#TypeConverter
public static String measurementsToString(List<Measurement> list) {
Gson gson = new Gson();
Type type = new TypeToken<List<Measurement>>() {}.getType();
String json = gson.toJson(list, type);
return json;
}
}
#Entity
#TypeConverters(ProductTypeConverter.class)
public class SortedAttribute {
#PrimaryKey
private int idProduct;
private String reference;
private List<Measurement> measurements = null;
}
EDIT: Use a type converter
#Relation is what you are looking for.
https://developer.android.com/reference/android/arch/persistence/room/Relation.html
From the Room docs:
#Entity
public class Pet {
# PrimaryKey
int petId;
String name;
}
public class UserNameAndAllPets {
public int userId;
public String name;
#Relation(parentColumn = "petId", entityColumn = "userId")
public List<Pet> pets;
}
#Dao
public interface UserPetDao {
#Query("SELECT petId, name from User")
public List<UserNameAndAllPets> loadUserAndPets();
}
Note: Upon further research, Room does not quite support lists of objects that are INSIDE objects. I (and others) have opted to handle the lists separately. Room can handle lists of objects just fine as long as they aren't within an object; so as long as your items inside the list are related to your overall object you can recover the list.
So, you would actually #Ignore the list and just handle it in your Dao abstract class. I could not find the SO post's I found earlier that portray this.
I've added one to many relationship in Room using Relation.
I referred to this post to write the following code for relation in Room.
The post tells how to read the values from the database but storing the entities into the database resulted in userId to be empty which means there is no relation between the 2 tables.
I'm not sure what is the ideal way to insert a User and List of Pet into the database while having userId value.
1) User Entity:
#Entity
public class User {
#PrimaryKey
public int id; // User id
}
2) Pet Entity:
#Entity
public class Pet {
#PrimaryKey
public int id; // Pet id
public int userId; // User id
public String name;
}
3) UserWithPets POJO:
// Note: No annotation required at this class definition.
public class UserWithPets {
#Embedded
public User user;
#Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
public List<Pet> pets;
}
Now to fetch the records from DB we use the following DAO:
#Dao
public interface UserDao {
#Insert
fun insertUser(user: User)
#Query("SELECT * FROM User")
public List<UserWithPets> loadUsersWithPets();
}
EDIT
I have created this issue https://issuetracker.google.com/issues/62848977 on the issue tracker. Hopefully they will do something regarding it.
You can do this by changing your Dao from an interface to an abstract class.
#Dao
public abstract class UserDao {
public void insertPetsForUser(User user, List<Pet> pets){
for(Pet pet : pets){
pet.setUserId(user.getId());
}
_insertAll(pets);
}
#Insert
abstract void _insertAll(List<Pet> pets); //this could go in a PetDao instead...
#Insert
public abstract void insertUser(User user);
#Query("SELECT * FROM User")
abstract List<UserWithPets> loadUsersWithPets();
}
You can also go further by having a User object have an #Ignored List<Pet> pets
#Entity
public class User {
#PrimaryKey
public int id; // User id
#Ignored
public List<Pet> pets
}
and then the Dao can map UserWithPets to User:
public List<User> getUsers() {
List<UserWithPets> usersWithPets = loadUserWithPets();
List<User> users = new ArrayList<User>(usersWithPets.size())
for(UserWithPets userWithPets: usersWithPets) {
userWithPets.user.pets = userWithPets.pets;
users.add(userWithPets.user);
}
return users;
}
This leaves you with the full Dao:
#Dao
public abstract class UserDao {
public void insertAll(List<User> users) {
for(User user:users) {
if(user.pets != null) {
insertPetsForUser(user, user.pets);
}
}
_insertAll(users);
}
private void insertPetsForUser(User user, List<Pet> pets){
for(Pet pet : pets){
pet.setUserId(user.getId());
}
_insertAll(pets);
}
public List<User> getUsersWithPetsEagerlyLoaded() {
List<UserWithPets> usersWithPets = _loadUsersWithPets();
List<User> users = new ArrayList<User>(usersWithPets.size())
for(UserWithPets userWithPets: usersWithPets) {
userWithPets.user.pets = userWithPets.pets;
users.add(userWithPets.user);
}
return users;
}
//package private methods so that wrapper methods are used, Room allows for this, but not private methods, hence the underscores to put people off using them :)
#Insert
abstract void _insertAll(List<Pet> pets);
#Insert
abstract void _insertAll(List<User> users);
#Query("SELECT * FROM User")
abstract List<UserWithPets> _loadUsersWithPets();
}
You may want to have the insertAll(List<Pet>) and insertPetsForUser(User, List<Pet>) methods in a PetDAO instead... how you partition your DAOs is up to you! :)
Anyway, it's just another option. Wrapping your DAOs in DataSource objects also works.
There is no native solution till any update in Room Library but you can do this by a trick. Find below mentioned.
Just Create a User with Pets (Ignore pets). Add getter and setter. Notice that we have to set our Id's manually later and can't use autogenerate.
#Entity
public class User {
#PrimaryKey
public int id;
#Ignore
private List<Pet> petList;
}
Create a Pet.
#Entity
public class Pet
{
#PrimaryKey
public int id;
public int userId;
public String name;
}
The UserDao should be an abstract class instead of an Interface. Then finally in your UserDao.
#Insert
public abstract void insertUser(User user);
#Insert
public abstract void insertPetList(List<Pet> pets);
#Query("SELECT * FROM User WHERE id =:id")
public abstract User getUser(int id);
#Query("SELECT * FROM Pet WHERE userId =:userId")
public abstract List<Pet> getPetList(int userId);
public void insertUserWithPet(User user) {
List<Pet> pets = user.getPetList();
for (int i = 0; i < pets.size(); i++) {
pets.get(i).setUserId(user.getId());
}
insertPetList(pets);
insertUser(user);
}
public User getUserWithPets(int id) {
User user = getUser(id);
List<Pet> pets = getPetList(id);
user.setPetList(pets);
return user;
}
Your problem can be solved by this without creating UserWithPets POJO.
As Room does not manage the Relations of the entities, you have to set the userId on each pet yourself and save them. As long as there are not too many pets at once, I'd use an insertAll method to keep it short.
#Dao
public interface PetDao {
#Insert
void insertAll(List<Pet> pets);
}
I don't think there's any better way at the moment.
To make the handling easier, I'd use an abstraction in the layer above the DAOs:
public void insertPetsForUser(User user, List<Pet> pets){
for(Pet pet : pets){
pet.setUserId(user.getId());
}
petDao.insertAll(pets);
}
Currently there is no native solution to this problem. I have created this https://issuetracker.google.com/issues/62848977 on Google's issue tracker and the Architecture Components Team said they will adding a native solution in or after v1.0 of Room library.
Temporary Workaround:
Meanwhile you can use the solution mentioned by tknell.
public void insertPetsForUser(User user, List<Pet> pets){
for(Pet pet : pets){
pet.setUserId(user.getId());
}
petDao.insertAll(pets);
}
I managed to insert it properly with a relatively simple workaround. Here are my entities:
#Entity
public class Recipe {
#PrimaryKey(autoGenerate = true)
public long id;
public String name;
public String description;
public String imageUrl;
public int addedOn;
}
#Entity
public class Ingredient {
#PrimaryKey(autoGenerate = true)
public long id;
public long recipeId;
public String name;
public String quantity;
}
public class RecipeWithIngredients {
#Embedded
public Recipe recipe;
#Relation(parentColumn = "id",entityColumn = "recipeId",entity = Ingredient.class)
public List<Ingredient> ingredients;
I am using autoGenerate for auto-increment value(long is used with a purpoes).
Here is my solution:
#Dao
public abstract class RecipeDao {
public void insert(RecipeWithIngredients recipeWithIngredients){
long id=insertRecipe(recipeWithIngredients.getRecipe());
recipeWithIngredients.getIngredients().forEach(i->i.setRecipeId(id));
insertAll(recipeWithIngredients.getIngredients());
}
public void delete(RecipeWithIngredients recipeWithIngredients){
delete(recipeWithIngredients.getRecipe(),recipeWithIngredients.getIngredients());
}
#Insert
abstract void insertAll(List<Ingredient> ingredients);
#Insert
abstract long insertRecipe(Recipe recipe); //return type is the key here.
#Transaction
#Delete
abstract void delete(Recipe recipe,List<Ingredient> ingredients);
#Transaction
#Query("SELECT * FROM Recipe")
public abstract List<RecipeWithIngredients> loadAll();
}
I had problem linking the entities, auto generate produced "recipeId=0" all the time. Inserting the recipe entity firstly fixed it for me.
Now at v2.1.0 Room seems to be not suitable for models with nested relations. It needed lots of boilerplate code to maintain them. E.g. manual insert of lists, creating and mapping local IDs.
This relations-mapping operations are done out of box by Requery https://github.com/requery/requery Additionaly it does not have issues with inserting Enums and have some converters for other complex types like URI.