Last Couple of days I have been spending times on learning new Android Architecture Components . After following up some blog posts, documentation & tutorials , every components were getting clear to me . But Suddenly I realised what about our old friend Content Provider . I might sound silly , because before writing this question I have spent quite a time searching , Am I be the only one came up with this question . I hadn't got any helpful solution . Anyways here is it , if I want to build up an app with local DB , I will now obviously choose new Architecture Components (live data , view model , room ) without any farther thinking this will be very helpful to make app 10x robust . But If I want my DB datas accessible to other app , for instance To Widget How do I integrate Content Provider with Room ?
I had the same question by the way. And I found a sample here which answers my question. Hope it does the same with you.
In short, this is in the DAO object which would be called from Content Provider's query() method.
/**
* Select all cheeses.
*
* #return A {#link Cursor} of all the cheeses in the table.
*/
#Query("SELECT * FROM " + Cheese.TABLE_NAME)
Cursor selectAll();
Notice how it returns Cursor object. Other operations, you can see for yourself in more detail in the sample.
This here is choice number 3 in the answer by #CommonsWare, I think.
if I want to build up an app with local DB , I will now obviously choose new Architecture Components (live data , view model , room )
I would not use the term "obviously" there. The Architecture Components are an option, but not a requirement.
But If I want my DB datas accessible to other app , for instance To Widget How do I integrate Content Provider with Room ?
An app widget is unrelated to a ContentProvider. IMHO very few apps should be exposing databases to third parties via ContentProvider, and no apps should be using a ContentProvider purely for internal purposes.
That being said, you have a few choices:
Do not use Room, at least for the tables to be exposed via the ContentProvider
Use Room for internal purposes, but then use classic SQLite programming techniques for the ContentProvider, by calling getOpenHelper() on your RoomDatabase
Use Room in the ContentProvider, writing your own code to build up a MatrixCursor from the Room entities that you retrieve (for query()) or creating the entities for use with other operations (for insert(), update(), delete(), etc.)
Room Library does not have any particular support for Content Provider. You can only write Content Provider on your own and then use Room to query a database.
If you want to use Android Architecture Components and you want to work with SQLite based Content Providers, consider using Kripton Persistence Library: it allows to generate Live Data from DB queries, generate Content Provider for you, and much more. Least but not last: why do you have to write the entire SQL, when you need only to write the where conditions?
Just to be clear, I'm the author of Kripton Persistence Library. I wrote it because I didn't find a unique library that fit all my need in terms of persistence management (and yes, because I like to program).
I wrote an converted version of Google Content Provider Sample with Kripton. You can found it here.
Just to simplify the reading. With Kripton, you only need to define a DAO interface. Content provider will be generated by the annotations. The same DAO converted in Kripton will be:
#BindContentProviderPath(path = "cheese")
#BindDao(Cheese.class)
public interface CheeseDao {
#BindSqlSelect(fields="count(*)")
int count();
#BindContentProviderEntry
#BindSqlInsert
long insert(String name);
#BindContentProviderEntry()
#BindSqlSelect
List<Cheese> selectAll();
#BindContentProviderEntry(path = "${id}")
#BindSqlSelect(where ="id=${id}")
Cheese selectById(long id);
#BindContentProviderEntry(path = "${id}")
#BindSqlDelete(where ="id=${id}")
int deleteById(long id);
#BindContentProviderEntry(path = "${cheese.id}")
#BindSqlUpdate(where="id=${cheese.id}")
int update(Cheese cheese);
}
The generated Content Provider exposes DAO's method with URIs. For clearification, I put here only the generated JavaDoc (always by Kripton).
More information about Kripton on its wiki, my site and on my articles .
Late post but I bumped in the same issue recently. Finally ended up in using the same Room Database instance for both local and content provider purpose.
So the app itself uses Room Database as usual and Content Provider "wraps" Room Database with "open helper" as follows:
class DatabaseProvider : ContentProvider() {
override fun onCreate(): Boolean {
return true
}
override fun query(uri: Uri?, projection: Array<out String?>?, selection: String?, selectionArgs: Array<out String?>?, sortOrder: String?): Cursor? {
val db = roomDatabase.openHelper.readableDatabase
db.query(...)
}
override fun insert(uri: Uri?, values: ContentValues?): Uri? {
val db = roomDatabase.openHelper.writableDatabase
db.insert(...)
}
override fun update(uri: Uri?, values: ContentValues?, selection: String?, selectionArgs: Array<out String?>?): Int {
val db = roomDatabase.openHelper.writableDatabase
db.update(...)
}
override fun delete(uri: Uri?, selection: String?, selectionArgs: Array<out String?>?): Int {
val db = roomDatabase.openHelper.writableDatabase
db.delete(...)
}
override fun getType(uri: Uri?): String? {
}
}
you'd better use the SupportOpenHelper
public class MyContentProvider extends ContentProvider {
public MyContentProvider() {
}
#Override
public String getType(Uri uri) {
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
throw new UnsupportedOperationException("Not yet implemented");
}
UserDatabase database;
#Override
public boolean onCreate() {
database = Room.databaseBuilder(getContext(), UserDatabase.class, "user.db").allowMainThreadQueries().build();
return false;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return database.query(SupportSQLiteQueryBuilder.builder("user").selection(selection, selectionArgs).columns(projection).orderBy(sortOrder).create());
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return database.getOpenHelper().getWritableDatabase().update("user", 0, values, selection, selectionArgs);
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return database.getOpenHelper().getWritableDatabase().delete("user", selection, selectionArgs);
}
#Override
public Uri insert(Uri uri, ContentValues values) {
long retId = database.getOpenHelper().getWritableDatabase().insert("user", 0, values);
return ContentUris.withAppendedId(uri, retId);
}
}
Related
Currently, we have the following database table
#Entity(
tableName = "note"
)
public class Note {
#ColumnInfo(name = "body")
private String body;
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
The length of the body string, can be from 0 to a very large number.
In certain circumstance, we need to
Load the all notes into memory.
A LiveData which is able to inform observers, if there's any changes made in the SQLite note table.
We just need the first 256 characters of body. We do not need entire body. Loading entire body string for all notes might cause OutOfMemoryException.
We have the following Room Database Dao
#Dao
public abstract class NoteDao {
#Query("SELECT * FROM note")
public abstract LiveData<List<Note>> getAllNotes();
}
getAllNotes able to fulfill requirements (1) and (2), but not (3).
The following getAllNotesWithShortBody is a failed solution.
#Dao
public abstract class NoteDao {
#Query("SELECT * FROM note")
public abstract LiveData<List<Note>> getAllNotes();
#Query("SELECT * FROM note")
public abstract List<Note> getAllNotesSync();
public LiveData<List<Note>> getAllNotesWithShortBody() {
MutableLiveData<List<Note>> notesLiveData = new MutableLiveData<>();
//
// Problem 1: Still can cause OutOfMemoryException by loading
// List of notes with complete body string.
//
List<Note> notes = getAllNotesSync();
for (Note note : notes) {
String body = note.getBody();
// Extract first 256 characters from body string.
body = body.substring(0, Math.min(body.length(), 256));
note.setBody(body);
}
notesLiveData.postValue(notes);
//
// Problem 2: The returned LiveData unable to inform observers,
// if there's any changes made in the SQLite `note` table.
//
return notesLiveData;
}
}
I was wondering, is there any way to tell Room database Dao: Before returning List of Notes as LiveData, please perform transformation on every Note's body column, by trimming the string to maximum 256 characters?
Examining the source code generated by Room Dao
If we look at the source code generated by Room Dao
#Override
public LiveData<List<Note>> getAllNotes() {
final String _sql = "SELECT * FROM note";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
...
...
final String _tmpBody;
_tmpBody = _cursor.getString(_cursorIndexOfBody);
_tmpPlainNote.setBody(_tmpBody);
It will be great, if there is a way to supply transformation function during runtime, so that we can have
final String _tmpBody;
_tmpBody = transform_function(_cursor.getString(_cursorIndexOfBody));
_tmpPlainNote.setBody(_tmpBody);
p/s Please do not counter recommend Paging library at this moment, as some of our features require entire List of Notes (with trimmed body String) in memory.
You can use SUBSTR, one of SQLite's built-in functions.
You need a primary key in your #Entity. Assuming that you call it id, you can write a SQL like below.
#Query("SELECT id, SUBSTR(body, 0, 257) AS body FROM note")
public abstract LiveData<List<Note>> getAllNotes();
This will return the body trimmed to 256 chars.
With that being said, you should consider segmenting your rows. If you have too many rows, they will eventually use up your memory at some point. Using Paging is one way to do it. You can also use LIMIT and OFFSET to manually go through segments of rows.
Im trying to make a simple android mediaplayer app that can be controlled from a distance. At the moment I'm trying to fix the issue of sending all the information on artists/albums/songs that are on the phone. At the moment I'm retrieving all the information as such:
private val contentResolver = activity.contentResolver!!
fun getAll():Set<Album>{
val res = mutableSetOf<Album>()
val cursor = contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
arrayOf(
MediaStore.Audio.Albums.ALBUM,
MediaStore.Audio.Albums.ALBUM_ART,
MediaStore.Audio.Albums.NUMBER_OF_SONGS,
MediaStore.Audio.Albums.ARTIST)
,null,null)
if(cursor!!.moveToFirst())
do {
res.add(Album().apply {
name = cursor.getString(0)
if (!cursor.getString(1).isNullOrEmpty())
albumArtUri = Uri.parse(cursor.getString(1))
songCount = cursor.getInt(2)
artist = Artist().apply {
name = cursor.getString(3)
}
})
cursor.moveToNext()
}while (!cursor.isAfterLast)
cursor.close()
return res
}
Seeing that I'm using a cursor, I thought I was working with a kind of database (SQLite or so) As you can see, this is a lot of code for just a set of objects with little information; the album objects created don't have the songs in them. For this you'd need to start a new query, starting and a new URI. Now I thought I could use an ORM. So I can actually fill the album objects with a list of songs and so on. I decided to try Jetbrains Exposed, typed:
val database = Database.connect(....)
and I'm at a loss, I don't know how to connect to this the database. I can't seem to find any examples on how to start with this.
Exposed is for JDBC. ContentResolver is not using JDBC, and the Cursor is not an object from JDBC. In general, Android does not use JDBC, in apps or at the OS level.
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.
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
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.