I am looking through the android calendar source code for the Instances Content Provider to see how it works and gets populated.
The reason being is that I am trying to replicate its workings in my app but I have not found where it gets populated from in the source.
I know the Instances database cannot be written to but somewhere it has to be getting written to since its getting populated. I just want to see how they do some of the calculations for the values.
The only thing I can find about the Instances is this but that does not tell me what I want to know and just tells me the the query and the uri's not the code behind the values.
does anyone know where it is?
The code that handles Content Provider URIs that start with content://com.android.calendar/instances/* is in packages/providers/CalendarProvider/src/com/android/providers/calendar/CalendarProvider2.java.
From that file it is possible to look up further implementation details. For example, a lot of the initialization code, like CREATE TABLE calls, is in CalendarDatabaseHelper.java in the same project.
I hope this helps!
Instances are being populated in "updateInstancesLocked" method in CalendarInstancesHelper.java. Also please check "performInstanceExpansion" method in the same file.
Link to the above mention method in GrepCode
I have provided the snippet of the method below
/**
* Updates the instances table when an event is added or updated.
* #param values The new values of the event.
* #param rowId The database row id of the event.
* #param newEvent true if the event is new.
* #param db The database
*/
private void updateInstancesLocked(ContentValues values,
long rowId,
boolean newEvent,
SQLiteDatabase db) {
// If there are no expanded Instances, then return.
MetaData.Fields fields = mMetaData.getFieldsLocked();
if (fields.maxInstance == 0) {
return;
}
Long dtstartMillis = values.getAsLong(Events.DTSTART);
if (dtstartMillis == null) {
if (newEvent) {
// must be present for a new event.
throw new RuntimeException("DTSTART missing.");
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Missing DTSTART. No need to update instance.");
}
return;
}
Long lastDateMillis = values.getAsLong(Events.LAST_DATE);
Long originalInstanceTime = values.getAsLong(Events.ORIGINAL_INSTANCE_TIME);
if (!newEvent) {
// Want to do this for regular event, recurrence, or exception.
// For recurrence or exception, more deletion may happen below if we
// do an instance expansion. This deletion will suffice if the exception
// is moved outside the window, for instance.
db.delete("Instances", "event_id=?", new String[] {String.valueOf(rowId)});
}
String rrule = values.getAsString(Events.RRULE);
String rdate = values.getAsString(Events.RDATE);
String originalEvent = values.getAsString(Events.ORIGINAL_EVENT);
if (isRecurrenceEvent(rrule, rdate, originalEvent)) {
// The recurrence or exception needs to be (re-)expanded if:
// a) Exception or recurrence that falls inside window
boolean insideWindow = dtstartMillis <= fields.maxInstance &&
(lastDateMillis == null || lastDateMillis >= fields.minInstance);
// b) Exception that affects instance inside window
// These conditions match the query in getEntries
// See getEntries comment for explanation of subtracting 1 week.
boolean affectsWindow = originalInstanceTime != null &&
originalInstanceTime <= fields.maxInstance &&
originalInstanceTime >= fields.minInstance - MAX_ASSUMED_DURATION;
if (insideWindow || affectsWindow) {
updateRecurrenceInstancesLocked(values, rowId, db);
}
// TODO: an exception creation or update could be optimized by
// updating just the affected instances, instead of regenerating
// the recurrence.
return;
}
Long dtendMillis = values.getAsLong(Events.DTEND);
if (dtendMillis == null) {
dtendMillis = dtstartMillis;
}
// if the event is in the expanded range, insert
// into the instances table.
// TODO: deal with durations. currently, durations are only used in
// recurrences.
if (dtstartMillis <= fields.maxInstance && dtendMillis >= fields.minInstance) {
ContentValues instanceValues = new ContentValues();
instanceValues.put(Instances.EVENT_ID, rowId);
instanceValues.put(Instances.BEGIN, dtstartMillis);
instanceValues.put(Instances.END, dtendMillis);
boolean allDay = false;
Integer allDayInteger = values.getAsInteger(Events.ALL_DAY);
if (allDayInteger != null) {
allDay = allDayInteger != 0;
}
// Update the timezone-dependent fields.
Time local = new Time();
if (allDay) {
local.timezone = Time.TIMEZONE_UTC;
} else {
local.timezone = fields.timezone;
}
computeTimezoneDependentFields(dtstartMillis, dtendMillis, local, instanceValues);
mDbHelper.instancesInsert(instanceValues);
}
}
Hope this helps!!!
Let me know if you are expecting something different
Related
I am fairly new to Android Room and SQLite in general, so sorry if this is a simple question.
I am getting data from a API that I'd like to insert into a database so it's accessible when the device is offline.
Depending on the endpoint of the API, some fields of my Data objects may be null (Think a summary with just the basic fields versus a fully detailed object with all fields)
To keep the database clean, I'd like to update the entries, but only the columns that are not null (eg. that I have new values for) and keep the rest of the columns untouched.
Here are some example classes to clarify:
Person
#Entity(tableName = "person", indices = {
#Index(value = "id", unique = true)
})
public class Person {
#PrimaryKey
public int id;
public String name;
public String description;
}
Example:
// create db
RoomDB db = RoomDB.create(ctx);
// create some sample objects
final Person p2 = new Person(2, "Peter", null);
// insert them into the db
db.personDao().insert(p2);
// create a updated peter that likes spiders
// but has no name (as a example)
final Person newPeter = new Person(2, null, "Peter likes spiders");
// and update him
db.personDao().updateNonNull(newPeter);
// now we read him back
final Person peter = db.personDao().getById(2);
In this example, the desired values of 'peter' would be:
id = 2
name = "Peter"
description = "Peter likes spiders"
However, using Room's #Update or #Insert i can only get this:
id = 2
name = null
description = "Peter likes spiders"
The only way i found to achive this would be to manuall get the object and supplement the values like so:
#Transaction
public void updateNonNull(Person newPerson) {
final Person oldPerson = getById(newPerson.id);
if (oldPerson == null) {
insert(newPerson);
return;
}
if (newPerson.name == null)
newPerson.name = oldPerson.name;
if (newPerson.description == null)
newPerson.description = oldPerson.description;
update(newPerson);
}
However, that would result in quite a bit of code with bigger objects...
So my question, is there a better way to do this?
Edit:
After some Testing with the SQL by #Priyansh Kedia, i found that those functions indeed work as intended and do so at a higher performance than java.
However, as a SQL statement would have required me to write huge queries, i decided to use a Reflection based solution, as can be seen below.
I only did so because the function isn't called regularly, so the lower performance won't matter too much.
/**
* merge two objects fields using reflection.
* replaces null value fields in newObj with the value of that field in oldObj
* <p>
* assuming the following values:
* oldObj: {name: null, desc: "bar"}
* newObj: {name: "foo", desc: null}
* <p>
* results in the "sum" of both objects: {name: "foo", desc: "bar"}
*
* #param type the type of the two objects to merge
* #param oldObj the old object
* #param newObj the new object. after the function, this is the merged object
* #param <T> the type
* #implNote This function uses reflection, and thus is quite slow.
* The fastest way of doing this would be to use SQLs' ifnull or coalesce (about 35% faster), but that would involve manually writing a expression for EVERY field.
* That is a lot of extra code which i'm not willing to write...
* Besides, as long as this function isn't called too often, it doesn't really matter anyway
*/
public static <T> void merge(#NonNull Class<T> type, #NonNull T oldObj, #NonNull T newObj) {
// loop through each field that is accessible in the target type
for (Field f : type.getFields()) {
// get field modifiers
final int mod = f.getModifiers();
// check this field is not status and not final
if (!Modifier.isStatic(mod)
&& !Modifier.isFinal(mod)) {
// try to merge
// get values of both the old and new object
// if the new object has a null value, set the value of the new object to that of the old object
// otherwise, keep the new value
try {
final Object oldVal = f.get(oldObj);
final Object newVal = f.get(newObj);
if (newVal == null)
f.set(newObj, oldVal);
} catch (IllegalAccessException e) {
Log.e("Tenshi", "IllegalAccess in merge: " + e.toString());
e.printStackTrace();
}
}
}
}
There is no in-built method in room to do this
What you can do is, put check in the query for your update method.
#Query("UPDATE person SET name = (CASE WHEN :name IS NOT NULL THEN :name ELSE name END), description = (CASE WHEN :description IS NOT NULL THEN :description ELSE description END) WHERE id = :id")
Person update(id: Int, name: String, description: String)
We have written the update query for SQL which checks if the inserted values are null or not, and if they are null, then the previous values are retained.
Buckle up folks, this is a weird one. I'm currently working on an android app that involves storing and retrieving data in an sqlite database. I was going through the app and testing some of the basic features to make sure everything worked, and lo and behold I found a bug in retrieving data from my database. When a user inputs their very first entry to the app, everything works as expected, the values get processed and stored. However, when I go back and attempt to access that data using SELECT * FROM history; I get a cursor that returns true when I call data.moveToNext(), yet when I loop through it using while(data.moveToNext()) { //get values and add to a List<> } the while loop never gets executed.
I've looked at the contents of the database after moving the file to my computer and opening the database using this db browser and I can see my entry.
Here's the method that I call to get all the points from my database:
List<PointValue> getAllPoints() {
List<PointValue> points;
Cursor data = rawQuery("SELECT * FROM history");
if (data.moveToNext()) {
points = new ArrayList<>();
while (data.moveToNext()) {
System.out.println("Looped");
long timestamp = data.getLong(data.getColumnIndexOrThrow("timestamp"));
int level = data.getInt(data.getColumnIndexOrThrow("level"));
points.add(new PointValue(timestamp, level));
}
} else {
return null;
}
data.close();
if (points.size() == 0) {
return null;
}
return points;
}
The rawQuery method looks like this:
private Cursor rawQuery(String sql) {
SQLiteDatabase db = this.getReadableDatabase();
return db.rawQuery(sql, null);
}
When I tried debugging this on my own, the size of points is 0 even though I know that there's at least one point in the database. Thoughts? The class containing all of my sql related stuff extends SQLiteOpenHelper
EDIT:
Here's the solution suggested by #Isaac Payne (still doesn't work):
public List<PointValue> getAllPoints() {
List<PointValue> points = new ArrayList<>();
Cursor data = rawQuery("SELECT * FROM history");
while (data.moveToNext()) {
long timestamp = data.getLong(data.getColumnIndexOrThrow("timestamp"));
int level = data.getInt(data.getColumnIndexOrThrow("level"));
points.add(new PointValue(timestamp, level));
}
data.close();
if (points.size() == 0) {
return null;
}
return points;
}
The issue is that when you call data.moveToNext() in the if statement you are moving to the first entry, then you call moveToNext() again in your while loop moving to the second non-existent entry. Try removing the if statement
Add data.moveToFirst() before if loop.
Cursor data = rawQuery("SELECT * FROM history");
//add this line
data.moveToFirst();
if (data.moveToNext()) {
Im using the ActiveAndroid library and I have read the entire information (very minimalist and insufficient unfortunately)
There is no mention whether the .save() operation is executed syncrhonously.
If it is asynchronous, how do I "listen" for it to end before proceeding?
http://www.activeandroid.com/ - this is the documentation I read
If you have a look at the source code of the Model class, you'll see that the save method does not do any thread handling:
public final Long save() {
final SQLiteDatabase db = Cache.openDatabase();
final ContentValues values = new ContentValues();
for (Field field : mTableInfo.getFields()) {
/* ... */
}
if (mId == null) {
mId = db.insert(mTableInfo.getTableName(), null, values);
}
else {
db.update(mTableInfo.getTableName(), values, idName+"=" + mId, null);
}
Cache.getContext().getContentResolver()
.notifyChange(ContentProvider.createUri(mTableInfo.getType(), mId), null);
return mId;
}
Saving thus occurs synchronously.
I am trying to fetch all the entries in a database table using SQLite. I ran a query, stored the values in a cursor and then via a loop I fetched all the values. However I can access all the entries except for the first one. Here is my code :
mydb1=new Database_CustomTransaction(getApplicationContext());
Cursor c12 = mydb1.executeQuery("Select * from table1");
System.out.println(c12);
if(c12 == null)
{
TextView nodataView = new TextView(this);
nodataView.setId(20);
nodataView.setText("No Data here !");
nodataView.setTextSize(20);
}
else
{
if(flagValue == false)
{
c12.moveToFirst();
flagValue = true;
}
while(c12.moveToNext())
{
type=c12.getString(0);
amount = c12.getInt(1);
spentOn = c12.getString(2);
date = c12.getString(3);
listType.add(i,type);
listSpentOn.add(i,spentOn);
listAmount.add(i,amount);
listDate.add(i,date);
i++;
}
}
latesttrans2.setAdapter(new TestAdapter2(this, listType, listSpentOn,listAmount,listDate));
Any ideas what I am doing wrong ?
c12.moveToFirst();
This moves to the first row.
while(c12.moveToNext())
This moves to the next row after the first row.
I would guess that the first call should be just dropped, but only you know what you intended with flagValue.
Use do while instead of using just while.I think it is skipping the first entry and straight away moving to next entry.
My application logs data about incoming phone calls and sms for particular dates and keeps them in a list. I want the application to check if there is already an entry for that date when a new phone call or sms comes in. If this is the case, I want the application to increment a value in the list.
However, when I try to do that I get this error: java.util.ConcurrentModificationException
How can I remedy this problem?
My codes looks like this
public void addLog(String phonenumber, String type, long date, int incoming, int outgoing)
{
//Check if log exists or else create it.
Log newLog = new Log(phonenumber, type, date, incoming, outgoing);
//Iterates through logs
for (Log log : logs)
{
if (log.getPhonenumber() == phonenumber && log.getDate() == date && log.getType() == type)
{
updateLog(newLog, log.getId());
}
else
{
android.util.Log.i("Datamodel", "Adding log");
logs.add(newLog);
//add to database
}
}
}
public void updateLog(Log newLog, long id)
{
//check for outgoing or incoming
if (newLog.getIncoming() == 1)
{
for (Log log : logs)
{
if (log.getId() == id)
{
//Increments incoming
int incoming = log.getIncoming();
android.util.Log.i("Datamodel", "Updating incoming");
log.setIncoming(incoming++);
}
else
{
//Increments outgoing
int outgoing = log.getOutgoing();
android.util.Log.i("Datamodel", "Updating outgoing");
log.setOutgoing(outgoing++);
}
}
}
//Update the list
//Add to database
}
A for loop, such as your for (Log log : logs), actually uses an Iterator underneath to iterate over the elements in the Collection (where logs is your Collection in this case).
A well-known fact about Iterator is that you must not attempt to modify the Collection while you're looping or iterating over it; to do otherwise will result in the ConcurrentModificationException.
There are already a large number of Q&As on SO regarding Iterator and CME, so rather than me duplicating advice, I advise looking at solutions offered here.