I am new in android
I have DAO query below
#Query("SELECT COUNT(*) FROM subject WHERE subID = :subID")
int getSubCount(String subID);
and Repository
public int getSubCount(String subID) {return subjectDAO.getSubCount(subID); }
and ViewModel
public int getSubCount(String subID) {
return repository.getSubCount(subID);
}
How can i get its value in my activity ?
Please help
I tried in activity
int lnCount = appActivityViewModel.getSubCount(lsSubject);
unfortunately, app crash
Related
I'm using Room library (MVVM Pattern), and one of the Dao functions returns this error message:
android.database.sqlite.SQLiteException: near "?": syntax error (code 1): , while compiling: UPDATE parcel_table SET possibleDeliveryPersonsList = ?,? WHERE id = ?
This is the Dao code:
#Dao
public interface ParcelDao {
#Insert
void insert(Parcel parcel);
#Delete
void delete(Parcel parcel);
#Query("UPDATE parcel_table SET shippingDate=:shippingDate WHERE id = :id")
void updateShippingDate(String shippingDate, int id);
#Query("UPDATE parcel_table SET parcelStatus=:status WHERE id = :id")
void updatePackageStatus(Enums.ParcelStatus status, int id);
#Query("UPDATE parcel_table SET deliveryPersonName=:deliveryPersonName WHERE id = :id")
void updateDeliveryPersonName(String deliveryPersonName, int id);
#Query("UPDATE parcel_table SET possibleDeliveryPersonsList = :possibleList WHERE id = :tid")
void updatePossibleDeliveryPersonsList(List<String> possibleList, int tid);
#Query("DELETE FROM parcel_table")
void deleteAllParcels();
#Query("SELECT * from parcel_table")
LiveData<List<Parcel>> getParcels();
}
And this is part of the Parcel class:
#Entity(tableName = "parcel_table")
public class Parcel {
private Enums.ParcelType parcelType;
private boolean isFragile;
private Enums.ParcelWeight parcelWeight;
private LatLng warehouseLocation;
private String recipientName;
private LatLng recipientAddress;
private String recipientEmail;
private String recipientPhone;
private String dateReceived;
private String shippingDate;
private Enums.ParcelStatus parcelStatus;
private String deliveryPersonName;
private String fireBasePushId;
private List<String> possibleDeliveryPersonsList;
#PrimaryKey(autoGenerate = true)
#NonNull
private int id;
//and more...
}
The List<String> Type Converter:
#TypeConverter
public String listToString(List<String> list) {
String joined = TextUtils.join(", ", list);
return joined;
}
#TypeConverter
public List<String> stringToList(String string) {
List<String> myList = new ArrayList<String>(Arrays.asList(string.split(",")));
return myList;
}
I have no idea what to do, because the SQLite code is supposedly automatically generated by the Dao and I have no effect on it ...
Two more workarounds in addition to Bob Snyder' answer (but they need to be thoroughfully tested):
To "imitate" TypeConverter (from List to String) by yourself (it's a tricky thing, I've not tried it in practice!):
In DAO change the type of possibleList to String:
#Query("UPDATE parcel_table SET possibleDeliveryPersonsList = :possibleList WHERE id = :tid")
void updatePossibleDeliveryPersonsList(String possibleList, int tid);
add auxiliary method for conversion (you can place it at DAO as well):
void updatePossibleDeliveryPersonsList(List<String> possibleList, int tid) {
String listToString = TextUtils.join(", ", possibleList);
// copied from your converter, it could be put in some common function to follow DRY
updatePossibleDeliveryPersonsList(listToString, tid);
}
and call it from Repository/ViewModel:
db.ParcelDao().updatePossibleDeliveryPersonsList(possibleList, tid);
To replace your multiple updateXXX methods in DAO with single update (you have a lot of fields in your table, may be it would be better to try some universal way to update any combinations of them?):
#Update
void update(Parcel parcel);
Add to your DAO method for searching parcel by id:
#Query("SELECT * from parcel_table where id = :id")
Parcel getParcel(int id);
And in your Repository/ViewModel at first get Parcel, then change it (status, name whatever) and then update database:
Parcel parcel = db.ParcelDao().getParcel(id); // let's say it can't be null
parcel.shippingDate = yourShippingDate; // or change here any of your other fields, including list
db.ParcelDao().update(parcel);
The documentation for the Query annotation explains a feature of Room argument binding:
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.
This feature was likely intended to be used in the where-clause (as shown in the documentation example), but appears to be applied everywhere in the query statement.
In your case, the desired behavior is to have Room apply your type converter, but instead Room is ignoring the type converter and generating the special list binding.
I think you are going to have to work around this limitation of the current Room implementation. You might want to write a Room bug-report to get confirmation that the explanation provided here is correct.
One option for workaround that will use the type converters is to define this class:
public class DeliveryPersonsUpdate {
public int id;
public List<String> deliveryPersons;
public DeliveryPersonsUpdate(int id, List<String> deliveryPersons) {
this.id = id;
this.deliveryPersons = deliveryPersons;
}
}
then add this method to your Dao:
#Update(entity = Parcel.class)
void update(DeliveryPersonsUpdate update);
Example invocation:
db.ParcelDao().update(new DeliveryPersonsUpdate(id, personsList);
I want to get the integer value from my Expense table which is a sum of the expenses(amount column) total expense.
In my Expense Entity class
//This is the column Amount which I want the sum of all values in this column and get single integer value total amount
#ColumnInfo(name = "Amount")
private int amount;
public int getAmount() {
return amount;
}
In my Dao class here is the function which give me total amount through LiveData
#Query("SELECT SUM(Amount) from Expense_table")
LiveData<Integer> getTotalExpenseAmount();
This is from Repository class
public LiveData<Integer> getTotalExpenseAmount() {
return expenseDao.getTotalExpenseAmount();
}
And this is from ViewModel class
public LiveData<Integer> getTotalExpenseAmount() {
return expenseRepository.getTotalExpenseAmount();
}
Everything works fine as it should be. But in my case I don't want to get this through liveData observer, instead I want to get this total amount value itself. Where I don't have to use the observer again and again
expenseViewModel.getTotalExpenseAmount().observe(MainActivity.this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
totalExpense = integer;
addDataSet();
Toast.makeText(MainActivity.this, "Total Expense " + totalExpense, Toast.LENGTH_SHORT).show();
}
});
I've to get this total amount value in many classes where I don't want to observe this again and again. Please tell me any shortcut to do this efficiently.
I'll be very thankful to you guys if you solve my problem
I create the database via Room and gave the path to C++ developers via JNI. All data are inserting from C++ side by using sqlpp, then i am executing SQL query and not getting fresh datas. If i kill app then start again, SQL query is returning all datas.
#Provides
#Singleton
LocalDatabase provideLocalDatabase(#DatabaseInfo String dbName, Context context) {
return Room.databaseBuilder(context, LocalDatabase.class, dbName)
.setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
.build();
}
MessageDao
#Dao
public abstract class MessageDao implements BaseDao<Message> {
#Query("SELECT * FROM messages")
public abstract Flowable<Message> getMessage();
}
Sending db path to C++
String uiDbPath = MyApp.applicationContext.getDatabasePath(AppConstants.DB_NAME).getAbsolutePath();
send_db_path_to_c_plus_plus(uiDbPath);
We are using JournalMode.WRITE_AHEAD_LOGGING mode, after inserting data to database i am checking the database to make sure all data are exits. As a result i am seeing all data, but select query is not returning. How can i solve the issue please help? Thanks in advance.
EDIT 1 C++ parts
int64_t insert(manager::db::connection & conn, int64_t conv_id, int64_t author_id, const std::string & data)
{
auto conn_guard = manager::db::make_connection_guard(conn);
{
LOG_TRACE("db::insert(in_msg) - BEGIN conv_id={0} author_id={1}", conv_id, author_id);
const auto tab_in_msg = manager::db::tables::InMsgs{};
auto tx = start_transaction(conn_guard.native());
conn_guard.native()(insert_into(tab_in_msg).set(tab_in_msg.conversationId = conv_id, tab_in_msg.authorId = author_id, tab_in_msg.data = data));
int64_t rowid = conn_guard.native().last_insert_id();
tx.commit();
LOG_TRACE("db::insert(in_msg) - END rowid={0}", rowid);
return rowid;
}
}
Let's take this example: I have a form, which has several sections, each having questions. Sideways, I have answers that are mapped to questions and they have another column that I want to filter on when querying:
So I have the following entities:
#Entity(tableName = "sections")
public class Section {
#PrimaryKey
public long id;
public String title;
}
#Entity(tableName = "questions")
public class Question {
#PrimaryKey
public long id;
public String title;
public long sectionId;
}
#Entity(tableName = "answers")
public class Answer {
#PrimaryKey
public long id;
public long questionId;
public int otherColumn;
}
In the section DAO I want to retrieve all of them.
Here's the POJO that I want filled by this query:
class SectionWithQuestions {
#Embedded
public Section section;
#Relation(parentColumn = "id", entityColumn = "sectionId", entity = Question.class)
public List<QuestionWithAnswer> questions;
public static class QuestionWithAnswer {
#Embedded
public Question question;
#Relation(parentColumn = "id", entityColumn = "questionId", entity = Answer.class)
List<Answer> answers;
}
}
In another application, the query would be:
SELECT s.*, q.*, a.*
FROM sections s
LEFT JOIN questions q ON q.sectionId = s.id
LEFT JOIN answers a ON a.questionId = q.id
WHERE s.id = :sectionId and a.otherColumn = :otherColumn
However in Room I have found out that if you want an object and their relations (like a user and its pets in the example), you only select the object, and the relations are queried in a second query. That would be:
#Query("SELECT * FROM sections WHERE id = :sectionId")
Then in the generated code there would be (pseudo code):
sql = "SELECT * FROM sections WHERE id = :sectionId" // what's inside #Query
cursor = query(sql)
int indexColumn1 = cursor.getColumnIndex(col1)
int indexColumn2
... etc
while (cursor.moveToNext) {
masterObject = new object()
masterObject.property1 = cursor.get(indexColumn1)
... etc
__fetchRelationshipXXXAsYYY(masterObject.relations) // fetch the child objects
}
and this __fetch XXX as YYY method is as follows:
sql = "SELECT field1, field2, ... FROM a WHERE foreignId IN (...)"
similar algo as previously: fetch column indices, and loop through the cursor
So basically it creates 2 queries: one for the master object and one for the relations. The 2nd query is automatically created and we have no control over it.
To get back to my problem where I want relations but also filter on the child column, I'm stuck:
in the 1st query I can't reference the otherColumn column because it doesn't exist
in the #Relation I can't either because the only properties of this annotation are the join column and entity definition
Is this possible in Room or do I have to make the subqueries myself?
Bonus question: why don't they join tables in a single query but create 2 queries instead? Is this for performance reasons?
Edit to clarify what I expected:
That's what I expected to write:
#Query("SELECT s.*, q.*, a.* " +
"FROM sections s " +
"LEFT JOIN questions q ON q.sectionId = s.id " +
"LEFT JOIN answers a ON a.questionId = q.id " +
"WHERE s.id = :sectionId and a.otherColumn = :additionalIntegerFilter")
SectionWithQuestionsAndAnswers fetchFullSectionData(long sectionId);
static class SectionWithQuestionsAndAnswers {
#Embedded Section section;
#Relation(parentColumn = "id", entityColumn = "sectionId", entity = Question.class)
List<QuestionWithAnswers> questions;
}
static class QuestionWithAnswers {
#Embedded Question question;
#Relation(parentColumn = "id", entityColumn = "questionId", entity = Answer.class)
Answer answer; // I already know that #Relation expects List<> or Set<> which is
// not useful if I know I have zero or one relation (ensured
// through unique keys)
}
That's pseudo code that I imagined to be implemented by Room as the generated code:
function fetchFullSectionData(long sectionId, long additionalIntegerFilter) {
query = prepare(sql); // from #Query
query.bindLong("sectionId", sectionId);
query.bindLong("additionalIntegerFilter", additionalIntegerFilter);
cursor = query.execute();
Section section = null;
long prevQuestionId = 0;
Question question = null;
while (cursor.hasNext()) {
if (section == null) {
section = new Section();
section.questions = new ArrayList<>();
section.field1 = cursor.get(col1); // etc for all fields
}
if (prevQuestionId != cursor.get(questionIdColId)) {
if (question != null) {
section.questions.add(question);
}
question = new Question();
question.fiedl1 = cursor.get(col1); // etc for all fields
prevQuestionId = question.id;
}
if (cursor.get(answerIdColId) != null) { // has answer
Answer answer = new Answer();
answer.field1 = cursor.get(col1); // etc for all fields
question.answer = answer;
}
}
if (section !=null && question != null) {
section.questions.add(question);
}
return section;
}
That's one query, and all my objects fetched.
I find Room Relations hard to work with, not very flexible and much of the work is done under the hood in a way that is hard to really be sure how.
In my projects, most of the time I just create presentation objects - objects dedicated for some UI presentation that can be filled with a custom select.
That way I have much more control over what I want to fetch from DB (i.e. what I really need), and I fill that into that custom presentation object.
I'm just pasting the information provided on the feature request I posted (see my comment on my question):
Hi there - we have recently released a new feature where relational query methods can be defined with Multimap return types. With this new feature, you should be able to achieve the results discussed in this thread. For more info on this new feature, you can check out the following resources:
Define relationships between objects: https://developer.android.com/training/data-storage/room/relationships
Relational Query Methods in ADS 2021: https://youtu.be/i5coKoVy1g4?t=344
The new MapInfo annotation: https://developer.android.com/reference/androidx/room/MapInfo
I know link-only answers aren't great, but I didn't have the opportunity to test this. If someone has a better answer, I'll accept it.
I found a better solution for this. Instead of aliasing all columns you can use #RawQuery annotation.
First of all, add a prefix for embedded table annotation using table name or its alias like #Embedded(prefix = "P.") or #Embedded(prefix = "Post."):
public class UserPost {
#Embedded
private User user;
#Embedded(prefix = "P.")
private Post post;
}
Then in your Dao, create a function to run a raw query, and create another function to run a raw query:
#Dao
public interface UserDao {
String USER_POST_QUERY = "SELECT U.*, P.* FROM User as U " +
"INNER JOIN Post as P ON U.id = P.userId " +
"WHERE P.status = 1";
#RawQuery
LiveData<List<UserPost>> rawQuery(SimpleSQLiteQuery query);
default LiveData<List<UserPost>> getAlertViolationsAsync() {
return rawQuery(new SimpleSQLiteQuery(USER_POST_QUERY));
}
}
In my application I am querying some data from realm about below model class.
After then I am sorting resulting data using realmResults.sort("point").
but result is not as expected.
Model class
public class FreeItem extends RealmObject {
private int itemId;
private int point;
private int freeQty;
private int freeItemId;
// geters setters
}
Querying code
if(realm==null||realm.isClosed()){
realm = Realm.getInstance(config);
}
PromotionPlan plan = realm.where(PromotionPlan.class).equalTo("id", planId).findFirst();
if(plan!=null) {
RealmResults<QtyDiscount> realmResults = plan.getDiscounts().where().equalTo("itemId", productId).findAll();
realmResults.sort("point");
return realmResults.subList(0,realmResults.size());
}
return new ArrayList<>();
Before sort( debug values)
FreeItem = [{itemId:61},{point:12},{freeQty:1},{freeItemId:61}]
FreeItem = [{itemId:61},{point:120},{freeQty:16},{freeItemId:61}]
FreeItem = [{itemId:61},{point:24},{freeQty:3},{freeItemId:61}]
FreeItem = [{itemId:61},{point:60},{freeQty:8},{freeItemId:61}]
After sort it is same as before.
So i tried to write own sorting process using below code, but it generates an exception and saying, since they are realm objects cannot replace.
Any help?
Sorting code
List<FreeItem> result = realmResults.subList(0, realmResults.size());
Collections.sort(result, new Comparator<FreeItem>() {
#Override
public int compare(FreeItem lhs, FreeItem rhs) {
if(lhs.getPoint()>rhs.getPoint())return 1;
if(lhs.getPoint()<rhs.getPoint())return -1;
return 0;
}
});
Exception
java.lang.UnsupportedOperationException: Replacing and element is not supported.
at io.realm.RealmResults$RealmResultsListIterator.set(RealmResults.java:826)
at io.realm.RealmResults$RealmResultsListIterator.set(RealmResults.java:757)
at java.util.AbstractList$SubAbstractList$SubAbstractListIterator.set(AbstractList.java:232)
at java.util.Collections.sort(Collections.java:1888)
Instead of using findAll() method you can use findAllSorted() and you can also give ASCENDING or DESCENDING order.
From Realm documentation:
findAllSorted(java.lang.String[] fieldNames, Sort[] sortOrders)
Finds all objects that fulfill the query conditions and sorted by
specific field names.
Parameters:
fieldNames - an array of field names to sort by.
sortOrders - how to sort the field names.
Returns: a RealmResults containing objects. If no objects match the condition, a list with zero objects is returned.
You have to use
plan.getDiscounts().where().equalTo("itemId", productId).findAllSorted("point",Sort.DESCENDING);
instead of
plan.getDiscounts().where().equalTo("itemId", productId).findAll();
realmResults.sort("point");
I hope it works for you.
This is the Updated version.. you can try
RealmResults<Notification_History>notification_histories=realm.where(Notification_History.class).findAll().sort("notification_count");