Android - Using ORMLite DAO as a ContentProvider - android

I have seen pattern C in the Google IO presentation and I am very anxious to implement this pattern. However, I do really like the ORMLite library and would like to use this library in my application as well.
When I say Google IO presentation I mean this one: https://www.youtube.com/watch?v=xHXn3Kg2IQE by Virgil Dobjanschi.
Now I have been searching a lot for an implementation that shows me how to use ORMLite in conjunction with Contentproviders.
Now my problem here is that the ORMLite DAO is conflicting with the Contentprovider. They essentially do the same and are a pain to integrate into each other. (Using Ormlite in Conjunction with Android's Content Provider others discussing this and agreeing upon this claim.)
A few libraries have implemented ORMLite into the contentprovider API pattern, one example is: https://github.com/blandware/android-atleap
However, underwater they still revert the model to ContentValues (simple types).
Android - Using Dao Pattern with contentProvider
This question is similair to my situation but 3 years ago and I'm suggesting an alternate solution below.
#jcwenger's answer is very useful, but I was wondering if anything has changed in the past 3 years. I'm facing the same issue and perhaps now since ORMLite has matured, it's more rewarding to use ORMLite?
My colleague next to me really, really wants to use ORMLite since he doesn't want to have to write any mapping himself. I know of the existance of the atleap and Android-OrmLiteContentProvider projects. These only provide a cursor to the activity and my colleague want to have lists of models or a single model. Can this be achieved?
My colleague suggests writing my own implementation of the Cursor, SyncAdapter? and Contentprovider (has to be done regardless) to work with models. However can the same functionality still be achieved with lists etc? Passing events to the activity to contentobservers etc?
Is this viable?
Edit
We'll most likely use the contentproviders privately. We do not need to expose these contentproviders. However the advantages that contentproviders provide are great. How else could I notify my GUI to update when the data has changed?
I also have to display data from multiple tables (joins and other data, not contained in the same table) in one activity and download images etc.

So since I couldn't find a proper answer, this is how I solved it after a while of trying:
public class CardProvider extends ContentProvider {
private InternalDatabase dbhelper;
private RuntimeExceptionDao<Card, UUID> cardDao;
/**
* Content authority for this provider.
*/
private static final String AUTHORITY = CardUris.CONTENT_AUTHORITY;
// The constants below represent individual URI routes, as IDs. Every URI pattern recognized by
// this ContentProvider is defined using sUriMatcher.addURI(), and associated with one of these
// IDs.
//
// When a incoming URI is run through sUriMatcher, it will be tested against the defined
// URI patterns, and the corresponding route ID will be returned.
/**
* URI ID for route: /cards
*/
public static final int ROUTE_CARDS = 1;
/**
* URI ID for route: /cards/{ID}
*/
public static final int ROUTE_CARDS_ID = 2;
/**
* UriMatcher, used to decode incoming URIs.
*/
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, "cards", ROUTE_CARDS);
sUriMatcher.addURI(AUTHORITY, "cards/*", ROUTE_CARDS_ID);
}
#Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
return 0;
}
#Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match) {
case ROUTE_CARDS:
return CardUris.CONTENT_CARDS;
case ROUTE_CARDS_ID:
return CardUris.CONTENT_ITEM_CARD;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
#Override
public Uri insert(Uri arg0, ContentValues arg1) {
// TODO Auto-generated method stub
return null;
}
#Override
public boolean onCreate() {
dbhelper = OpenHelperManager.getHelper(getContext(), InternalDatabase.class);
cardDao = dbhelper.getRuntimeExceptionDao(Card.class);
return true;
}
#Override
public Cursor query(Uri uri, String[] arg1, String arg2, String[] arg3,
String arg4) {
int uriMatch = sUriMatcher.match(uri);
switch (uriMatch) {
case ROUTE_CARDS_ID:
/*String id = uri.getLastPathSegment();
Card card = null;
try {
card = cardDao.queryBuilder().where().eq(Entry.ID_FIELD_NAME, id).queryForFirst();
} catch (SQLException e) {
e.printStackTrace();
}*/
//return null;
case ROUTE_CARDS:
// Return all known entries.
// Note: Notification URI must be manually set here for loaders to correctly
// register ContentObservers.
// build your query
QueryBuilder<Card, UUID> qb = cardDao.queryBuilder();
// when you are done, prepare your query and build an iterator
CloseableIterator<Card> iterator = null;
Cursor cursor = null;
try {
//qb.query();
iterator = cardDao.iterator(qb.where().eq("relevant", 1).and().eq("removed", false).prepare());
// get the raw results which can be cast under Android
AndroidDatabaseResults results =
(AndroidDatabaseResults)iterator.getRawResults();
cursor = results.getRawCursor();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//iterator.closeQuietly();
}
cursor.setNotificationUri(this.getContext().getContentResolver(), uri);
return cursor;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
#Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
// TODO Auto-generated method stub
return 0;
}
}
You could probably give purpose to the insert, update and delete methods, but the dao does this too and is what I am using.

Related

how to use context.getContentResolver() to execute a update with CASE WHEN statement

Having a sql statement with CASE to do the update field based on row id, without need to passing values.
"UPDATE accounts SET field= CASE WHEN id=(select id from accounts where id=0) THEN 1 ELSE 0 END";
How to use context.getContentResolver() to execute it? Or any other way?
if you go with ContentProvider and Path approach I'll suggest you to use some helper class:
public static class UriBuilder{
public static final String FRAGMENT_RAW_UPDATE = "rawUpdate"; // here could be noNotify, conflict resolver pathes, etc.
private Uri.Builder uri;
public UriBuilder(Uri uri){
this.uri = uri.buildUpon();
}
public UriBuilder(String uri){
this.uri = Uri.parse(uri).buildUpon();
}
public UriBuilder append(String path){
uri.appendPath(path);
return this;
}
public UriBuilder append(long id){//points directly to item
uri.appendPath(String.valueOf(id));
return this;
}
public UriBuilder rawUpdate(){
uri.fragment(FRAGMENT_RAW_UPDATE);
return this;
}
public Uri build(){
return uri.build();
}
public static boolean isRawUpdate(Uri uri) {
return FRAGMENT_RAW_UPDATE.equals(uri.getFragment());
}
}
In your content provider you better to have some helper methods to create URI with your brand new UriBuilder, something like:
public static Uri contentUri(String path, long id){
return new UriBuilder(BASE_URI)
.append(path)
.append(id)//optional
.build();
}
public static Uri contentUriRawUpdate(String path){
return new UriBuilder(BASE_URI)
.append(path)
.rawUpdate()
.build();
}
After you have all this in your code life would me much easier. To create raw update URI:
contentResolver.update(YourContentProvider.contentUriRawUpdate(DbContract.Table.CONTENT_URI), null, rawSql, null);
and finally in your ContentProvider's update:
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
if(UriBuilder.isRawUpdate(uri)){
dbHelper.getWritableDatabase().update(...);
return;// early exit
}
... // standard logic for matchers here
... // dbHelper.getWritableDatabase().update(...);
... // notify observers here
}
UPDATE:
I suggest that you understand risks and your ContentProvider would not be Public. Using this approach you can execute any SQL and in terms of security that is backdoor :)
If your ContentProvider is backed by a SQLite database, the ContentProvider itself can do the UPDATE statement that you want, using execSQL().
To specify that you want this specific UPDATE to be done, you can:
Use call() on ContentResolver, which triggers call() on your ContentProvider. This basically lets you invent your own protocol, for requests that do not fit the normal pattern.
Or, you can use a dedicated path on your Uri, along with update(). For example, if normally you are using content://your.authority/stuff to access the provider, use content://your.authority/stuff/special_update with update() to signal to the ContentProvider that you want this special UPDATE to be done.

How to test class using content resolver/provider?

I'm trying to test class that queries content resolver.
I would like to use MockContentResolver and mock query method.
The problem is that this method is final. What should I do? Use mocking framework? Mock other class? Thanks in advance.
public class CustomClass {
private ContentResolver mContentResolver;
public CustomClass(ContentResolver contentResolver) {
mContentResolver = contentResolver;
}
public String getConfig(String key) throws NoSuchFieldException {
String value = null;
Cursor cursor = getContentResolver().query(...);
if (cursor.moveToFirst()) {
//...
}
//..
}
}
Here is an example test that returns mock data from a content provider using getContentResolver().query.
It should work for any content provider, with a few modifications, but this example mocks returning phone numbers from the Contacts content provider
Here are the general steps:
Creates appropriate cursor using MatrixCursor
Extend MockContentProvider to return the created cursor
Add the provider to a MockContentResolver using the addProvider and setContentResolver
Add the MockContentResolver to an extended MockContext
Passes the context into the the class under test
Because query is a final method, you need to mock not only MockContentProvider but also MockContentResolver. Otherwise you will get an error when acquireProvider is called during the query method.
Here is the example code:
public class MockContentProviderTest extends AndroidTestCase{
public void testMockPhoneNumbersFromContacts(){
//Step 1: Create data you want to return and put it into a matrix cursor
//In this case I am mocking getting phone numbers from Contacts Provider
String[] exampleData = {"(979) 267-8509"};
String[] examleProjection = new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER};
MatrixCursor matrixCursor = new MatrixCursor(examleProjection);
matrixCursor.addRow(exampleData);
//Step 2: Create a stub content provider and add the matrix cursor as the expected result of the query
HashMapMockContentProvider mockProvider = new HashMapMockContentProvider();
mockProvider.addQueryResult(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, matrixCursor);
//Step 3: Create a mock resolver and add the content provider.
MockContentResolver mockResolver = new MockContentResolver();
mockResolver.addProvider(ContactsContract.AUTHORITY /*Needs to be the same as the authority of the provider you are mocking */, mockProvider);
//Step 4: Add the mock resolver to the mock context
ContextWithMockContentResolver mockContext = new ContextWithMockContentResolver(super.getContext());
mockContext.setContentResolver(mockResolver);
//Example Test
ExampleClassUnderTest underTest = new ExampleClassUnderTest();
String result = underTest.getPhoneNumbers(mockContext);
assertEquals("(979) 267-8509",result);
}
//Specialized Mock Content provider for step 2. Uses a hashmap to return data dependent on the uri in the query
public class HashMapMockContentProvider extends MockContentProvider{
private HashMap<Uri, Cursor> expectedResults = new HashMap<Uri, Cursor>();
public void addQueryResult(Uri uriIn, Cursor expectedResult){
expectedResults.put(uriIn, expectedResult);
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){
return expectedResults.get(uri);
}
}
public class ContextWithMockContentResolver extends RenamingDelegatingContext {
private ContentResolver contentResolver;
public void setContentResolver(ContentResolver contentResolver){ this.contentResolver = contentResolver;}
public ContextWithMockContentResolver(Context targetContext) { super(targetContext, "test");}
#Override public ContentResolver getContentResolver() { return contentResolver; }
#Override public Context getApplicationContext(){ return this; } //Added in-case my class called getApplicationContext()
}
//An example class under test which queries the populated cursor to get the expected phone number
public class ExampleClassUnderTest{
public String getPhoneNumbers(Context context){//Query for phone numbers from contacts
String[] projection = new String[]{ ContactsContract.CommonDataKinds.Phone.NUMBER};
Cursor cursor= context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, null);
cursor.moveToNext();
return cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
}
}
}
If you don't want to pass context in:
If you wanted to have it returned by getContext() in the class under test instead of passing it in you should be able to override getContext() in your android test like this
#Override
public Context getContext(){
return new ContextWithMockContentResolver(super.getContext());
}
This question is pretty old but people might still face the issue like me, because there is not a lot of documentation on testing this.
For me, for testing class which was dependent on content provider (from android API) I used ProviderTestCase2
public class ContactsUtilityTest extends ProviderTestCase2<OneQueryMockContentProvider> {
private ContactsUtility contactsUtility;
public ContactsUtilityTest() {
super(OneQueryMockContentProvider.class, ContactsContract.AUTHORITY);
}
#Override
protected void setUp() throws Exception {
super.setUp();
this.contactsUtility = new ContactsUtility(this.getMockContext());
}
public void testsmt() {
String phoneNumber = "777777777";
String[] exampleData = {phoneNumber};
String[] examleProjection = new String[]{ContactsContract.PhoneLookup.NUMBER};
MatrixCursor matrixCursor = new MatrixCursor(examleProjection);
matrixCursor.addRow(exampleData);
this.getProvider().addQueryResult(matrixCursor);
boolean result = this.contactsUtility.contactBookContainsContact(phoneNumber);
// internally class under test use this.context.getContentResolver().query(); URI is ContactsContract.PhoneLookup.CONTENT_FILTER_URI
assertTrue(result);
}
}
public class OneQueryMockContentProvider extends MockContentProvider {
private Cursor queryResult;
public void addQueryResult(Cursor expectedResult) {
this.queryResult = expectedResult;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return this.queryResult;
}
}
It's written by using Jenn Weingarten's answer.
Few things to note:
-your MockContentProvider must be public
-you must use Context from method this.getMockContext() instead of this.getContext() in your class under test, otherwise you will access not mock data but real data from device (in this case - contacts)
-Test must not be run with AndroidJUnit4 runner
-Test of course must be run as android instrumented test
-Second parameter in constructor of the test (authority) must be same compared to URI queried in class under test
-Type of mock provider must be provided as class parameter
Basically ProviderTestCase2 makes for you initializing mock context, mock content resolver and mock content provider.
I found it much more easier to use older method of testing instead of trying to write local unit test with mockito and junit4 for class which is highly dependent on android api.
Here is an example about how to stub a ContentResolver with mockk Library and Kotlin.
NOTE: this test seems that is not working if you run this in an emulator, fails in an emulator with API 23 with this error "java.lang.ClassCastException: android.database.MatrixCursor cannot be cast to java.lang.Boolean".
Clarified that, lets do this. Having an extension from Context object, that is called, val Context.googleCalendars: List<Pair<Long, String>>, this extension filters calendars witch calendar name doesn't ends with "#google.com", I am testing the correct behavior of this extension with this AndroidTest.
Yes you can download the repo from here.
#Test
fun getGoogleCalendarsTest() {
// mocking the context
val mockedContext: Context = mockk(relaxed = true)
// mocking the content resolver
val mockedContentResolver: ContentResolver = mockk(relaxed = true)
val columns: Array<String> = arrayOf(
CalendarContract.Calendars._ID,
CalendarContract.Calendars.NAME
)
// response to be stubbed, this will help to stub
// a response from a query in the mocked ContentResolver
val matrixCursor: Cursor = MatrixCursor(columns).apply {
this.addRow(arrayOf(1, "username01#gmail.com"))
this.addRow(arrayOf(2, "name02")) // this row must be filtered by the extension.
this.addRow(arrayOf(3, "username02#gmail.com"))
}
// stubbing content resolver in the mocked context.
every { mockedContext.contentResolver } returns mockedContentResolver
// stubbing the query.
every { mockedContentResolver.query(CalendarContract.Calendars.CONTENT_URI, any(), any(), any(), any()) } returns matrixCursor
val result: List<Pair<Long, String>> = mockedContext.googleCalendars
// since googleCalendars extension returns only the calendar name that ends with #gmail.com
// one row is filtered from the mocked response of the content resolver.
assertThat(result.isNotEmpty(), Matchers.`is`(true))
assertThat(result.size, Matchers.`is`(2))
}
After reading docs I was able to write MockContentProvider that implemented return of appropriate cursors. Then I added this provider to MockContentResolver using addProvider.
I haven't used Mockito yet, but for content providers, you can rely on Robolectric. https://github.com/juanmendez/jm_android_dev/blob/master/16.observers/00.magazineAppWithRx/app/src/test/java/ContentProviderTest.java

Android: How to create my own cursor class?

I'm using Sqlite in Android and to get a value from the database I use something like this:
Cursor cursor = sqliteDatabase.rawQuery("select title,category from table", null);
int columnIndexTitle = cursor.getColumnIndex("title");
iny columnIndexCategory = cursor.getColumnIndex("category");
cursor.moveToFirst();
while (cursor.moveToNext()) {
String title = cursor.getString(columnIndexTitle);
String category = cursor.getString(columnIndexCategory);
}
cursor.close();
I want to create my own Cursor so that I can do getColumnIndex() and getString() with one method. Something like this:
String title = cursor.getString("title");
I want to create my own class that extends the cursor that I get from sqliteDatabase.rawQuery, but I'm not sure how to accomplish this. Should I extend SQLiteCursor or how should I do this? Is it even a possible and is it a good idea?
I came across this question looking for the best way to create a custom Cursor to use together with a SQLiteDatabase. In my case I needed an extra attribute to the Cursor to carry an additional piece of information, so my use case is not exactly as in the body of the question. Posting my findings in hope it will be helpful.
The tricky part for me was that the SQLiteDatabase query methods returns a Cursor, and I needed to pass on a custom subclass to Cursor.
I found the solution in the Android API: Use the CursorWrapper class. It seems to be designed exactly for this.
The class:
public class MyCustomCursor extends CursorWrapper {
public MyCustomCursor(Cursor cursor) {
super(cursor);
}
private int myAddedAttribute;
public int getMyAddedAttribute() {
return myAddedAttribute;
}
public void setMyAddedAttribute(int myAddedAttribute) {
this.myAddedAttribute = myAddedAttribute;
}
}
Usage:
public MyCustomCursor getCursor(...) {
SQLiteDatabase DB = ...;
Cursor rawCursor = DB.query(...);
MyCustomCursor myCursor = new MyCustomCursor(rawCursor);
myCursor.setMyAddedAttribute(...);
return myCursor;
}
Creating your own getString will cause a map lookup for each call instead of only for getColumnIndex.
Here's the code for SQLiteCursor.getColumnIndex and AbstractCursor.getColumnIndex. If you have many rows, reducing calls to this function will prevent unnecessary string processing and map lookups.
I wouldn't extend it, I'd make a helper:
class MartinCursor {
private Cursor cursor;
MartinCursor(Cursor cursor) {
this.cursor = cursor;
}
String getString(String column) {
....
}
}
or
class MartinCursorHelper {
static String getString(Cursor cursor, String column) {
....
}
}
Personally, I'd do the latter, unless you hate providing this extra argument all the time.
EDIT: I forgot to mention pydave's important point: If you call this in a loop, you're setting yourself up for a noticeable performance impact. The preferred way is to lookup the index once, cache it, and use that instead.
You should make use of the DatabaseUtils.stringForQuery() static method that is already in Android SDK to easily retrieve a value, this example is for String bot there is also method for Long
stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs)
Utility method to run the query on the db and return the value in the first column of the first row.
Something like
String myString=DatabaseUtils.stringForQuery(getDB(),query,selectionArgs);
Came across this looking for a different solution, but just want to add this since I believe the answers are unsatisfactory.
You can easily create your own cursor class. In order to allow functions requiring Cursor to accept it, it must extend AbstractCursor. To overcome the issue of system not using your class, you simply make your class a wrapper.
There is a really good example here.
https://android.googlesource.com/platform/packages/apps/Contacts/+/8df53636fe956713cc3c13d9051aeb1982074286/src/com/android/contacts/calllog/ExtendedCursor.java
public class ExtendedCursor extends AbstractCursor {
/** The cursor to wrap. */
private final Cursor mCursor;
/** The name of the additional column. */
private final String mColumnName;
/** The value to be assigned to the additional column. */
private final Object mValue;
/**
* Creates a new cursor which extends the given cursor by adding a column with a constant value.
*
* #param cursor the cursor to extend
* #param columnName the name of the additional column
* #param value the value to be assigned to the additional column
*/
public ExtendedCursor(Cursor cursor, String columnName, Object value) {
mCursor = cursor;
mColumnName = columnName;
mValue = value;
}
#Override
public int getCount() {
return mCursor.getCount();
}
#Override
public String[] getColumnNames() {
String[] columnNames = mCursor.getColumnNames();
int length = columnNames.length;
String[] extendedColumnNames = new String[length + 1];
System.arraycopy(columnNames, 0, extendedColumnNames, 0, length);
extendedColumnNames[length] = mColumnName;
return extendedColumnNames;
}
That's the general idea of how it will work.
Now to the meat of the problem. To prevent the performance hit, create a hash to hold the column indices. This will serve as a cache. When getString is called, check the hash for the column index. If it does not exist, then fetch it with getColumnIndex and cache it.
I'm sorry I can't add any code currently, but I'm on mobile so I'll try to add some later.

Android: SQLite transactions when using ContentResolver

The goal: refresh database from XML data
The process:
Start transaction
Delete all existing rows from the tables
Per each main element of parsed XML insert row into main table and get PK
Per each child of the main element insert record into 2nd table providing FK from the previous step
Commit transaction
Pretty standard stuff as far as db operations. The problem is that CRUD operations are not done within ContentProvider but rather using ContentResolver so the insert for example looks like resolver.insert(CONTENT_URI, contentValues). The ContentResolver API doesn't seem to have anything pertained to transaction and I cannot use bulkInsert since I'm inserting in 2 tables intermittently (plus I want to have delete inside the transaction as well).
I was thinking of registering my customized ContentProvider as listener by using registerContentObserver but since ContentResolver#acquireProvider methods are hidden how do I obtain the right reference?
Am I out of luck?
I've seen that in the source code of Google I/O application, they override ContentProvider's applyBatch() method and use transactions inside of it. So, you create a batch of ContentProviderOperation s and then call getContentResolver().applyBatch(uri_authority, batch).
I'm planning to use this approach to see how it works. I'm curious if anyone else has tried it.
It is possible to do transaction based multi table inserts rather cleanly since Android 2.1 by using ContentProviderOperation, as mentioned by kaciula.
When you build the ContentProviderOperation object, you can call .withValueBackReference(fieldName, refNr). When the operation is applied using applyBatch, the result is that the ContentValues object that is supplied with the insert() call will have an integer injected. The integer will be keyed with the fieldName String, and its value is retrieved from the ContentProviderResult of a previously applied ContentProviderOperation, indexed by refNr.
Please refer to the code sample below. In the sample, a row is inserted in table1, and the resulting ID (in this case "1") is then used as a value when inserting the row in table 2. For brevity, the ContentProvider is not connected to a database. In the ContentProvider, there are printouts where it would be suitable to add the transaction handling.
public class BatchTestActivity extends Activity {
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ArrayList<ContentProviderOperation> list = new
ArrayList<ContentProviderOperation>();
list.add(ContentProviderOperation.
newInsert(BatchContentProvider.FIRST_URI).build());
ContentValues cv = new ContentValues();
cv.put("name", "second_name");
cv.put("refId", 23);
// In this example, "refId" in the contentValues will be overwritten by
// the result from the first insert operation, indexed by 0
list.add(ContentProviderOperation.
newInsert(BatchContentProvider.SECOND_URI).
withValues(cv).withValueBackReference("refId", 0).build());
try {
getContentResolver().applyBatch(
BatchContentProvider.AUTHORITY, list);
} catch (RemoteException e) {
e.printStackTrace();
} catch (OperationApplicationException e) {
e.printStackTrace();
}
}
}
public class BatchContentProvider extends ContentProvider {
private static final String SCHEME = "content://";
public static final String AUTHORITY = "com.test.batch";
public static final Uri FIRST_URI =
Uri.parse(SCHEME + AUTHORITY + "/" + "table1");
public static final Uri SECOND_URI =
Uri.parse(SCHEME + AUTHORITY + "/" + "table2");
public ContentProviderResult[] applyBatch(
ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
System.out.println("starting transaction");
ContentProviderResult[] result;
try {
result = super.applyBatch(operations);
} catch (OperationApplicationException e) {
System.out.println("aborting transaction");
throw e;
}
System.out.println("ending transaction");
return result;
}
public Uri insert(Uri uri, ContentValues values) {
// this printout will have a proper value when
// the second operation is applied
System.out.println("" + values);
return ContentUris.withAppendedId(uri, 1);
}
// other overrides omitted for brevity
}
All right - so this does not dingle aimlessly: the only way I can think of is to code startTransaction and endTransaction as URL-based query requests. Something like ContentResolver.query(START_TRANSACTION, null, null, null, null). Then in ContentProvider#query based on the registered URL call start or end transaction
You can get the implementation of the content provider object itself (if in the same process, hint: you can control the provider's process with multiprocess="true" or process="" http://developer.android.com/guide/topics/manifest/provider-element.html) using ContentProviderClient.getLocalContentProvider () which can be casted to your provider implementation which can provide extra functionality like a reset() that closes and deletes the database and you can also return a custom Transaction class instance with save() and close() methods.
public class Transaction {
protected Transaction (SQLiteDatabase database) {
this.database = database;
database.beginTransaction ();
}
public void save () {
this.database.setTransactionSuccessful ();
}
public void close () {
this.database.endTransaction ();
}
private SQLiteDatabase database;
}
public Transaction createTransaction () {
return new Transaction (this.dbHelper.getWritableDatabase ());
}
Then:
ContentProviderClient client = getContentResolver ().acquireContentProviderClient (Contract.authorityLocal);
Transaction tx = ((LocalContentProvider) client.getLocalContentProvider ()).createTransaction ();

Is there a way to access the calendar's entries without using gdata-java-client?

Is it possible to get the calendar's entries from the phone offline? It seem the only way is to use gdata-java-client.
Josef and Isaac's solutions for accessing the calendar only work in Android 2.1 and earlier. Google have changed the base content URI in 2.2 from "content://calendar" to "content://com.android.calendar". This change means the best approach is to attempt to obtain a cursor using the old base URI, and if the returned cursor is null, then try the new base URI.
Please note that I got this approach from the open source test code that Shane Conder and Lauren Darcey provide with their Working With The Android Calendar article.
private final static String BASE_CALENDAR_URI_PRE_2_2 = "content://calendar";
private final static String BASE_CALENDAR_URI_2_2 = "content://com.android.calendar";
/*
* Determines if we need to use a pre 2.2 calendar Uri, or a 2.2 calendar Uri, and returns the base Uri
*/
private String getCalendarUriBase() {
Uri calendars = Uri.parse(BASE_CALENDAR_URI_PRE_2_2 + "/calendars");
try {
Cursor managedCursor = managedQuery(calendars, null, null, null, null);
if (managedCursor != null) {
return BASE_CALENDAR_URI_PRE_2_2;
}
else {
calendars = Uri.parse(BASE_CALENDAR_URI_2_2 + "/calendars");
managedCursor = managedQuery(calendars, null, null, null, null);
if (managedCursor != null) {
return BASE_CALENDAR_URI_2_2;
}
}
} catch (Exception e) { /* eat any exceptions */ }
return null; // No working calendar URI found
}
These answers are good, but they all involve hard-coding the Calendar URI (which I've seen in three different incarnations across different Android devices).
A better way to get that URI (which hard-codes the name of a class and a field instead) would be something like this:
Class<?> calendarProviderClass = Class.forName("android.provider.Calendar");
Field uriField = calendarProviderClass.getField("CONTENT_URI");
Uri calendarUri = (Uri) uriField.get(null);
This isn't perfect (it will break if they ever remove the android.provider.Calendar class or the CONTENT_URI field) but it works on more platforms than any single URI hard-code.
Note that these reflection methods will throw exceptions which will need to be caught or re-thrown by the calling method.
Currently, this is not possible without using private APIs (see Josef's post.) There is a Calendar provider, but it is not public yet. It could change anytime and break your app.
Though, it probably will not change (I don't think they will change it from "calendar"), so you might be able to use it. But my recommendation is to use a separate class like this:
public class CalendarProvider {
public static final Uri CONTENT_URI = Uri.parse("content://calendar");
public static final String TITLE = "title";
public static final String ....
And use those instead of the strings directly. This will let you change it very easily if/when the API changes or it is made public.
You can use the calendar content provider (com.android.providers.calendar.CalendarProvider). Example:
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = contentResolver.query(Uri.parse("content://calendar/events"), null, null, null, null);
while(cursor.moveToNext()) {
String eventTitle = cursor.getString(cursor.getColumnIndex("title"));
Date eventStart = new Date(cursor.getLong(cursor.getColumnIndex("dtstart")));
// etc.
}
edit: you might want to put this in a wrapper (see Isaac's post) as it's currently a private API.
You can use the CalendarContract from here: https://github.com/dschuermann/android-calendar-compatibility
It is the same API class as available on Android 4, but made to work with Android >= 2.2.
About the API that can change... The whole ContentProvider approach won't change that quickly so can already overcome a lot of problems by only updating the strings. Therefor create constants you reuse over the whole project.
public static final String URI_CONTENT_CALENDAR_EVENTS = "content://calendar/events";
ContentResolver contentResolver = context.getContentResolver();
Cursor cursor = contentResolver.query(Uri.parse(URI_CONTENT_CALENDAR_EVENTS), null, null, null, null);
//etc
If you want a proper private API you'll have to create a pojo and some services like this:
public class CalendarEvent {
private long id;
private long date;
//etc...
}
public interface CalendarService {
public Set<CalendarEvent> getAllCalendarEvents();
public CalendarEvent findCalendarEventById(long id);
public CalendarEvent findCalendarEventByDate(long date);
}
and so on. This way you'll only have to update the CalendarEvent object and this service in case the API changes.
Nick's solution involves managedQuery, which is not defined in the Context class. Many times when you are running things in the background you would want to use a context object. Here's a modified version:
public String getCalendarUriBase() {
return (android.os.Build.VERSION.SDK_INT>=8)?
"content://com.android.calendar":
"content://calendar";
}
The catch for null should not be carried out here since there might be more exceptions even if the managedQuery succeeded earlier.

Categories

Resources