I am writing a unit test in my Android app to test a ContentProvider. This test extends ProviderTestCase2. I have the following code:
// Tests the MIME type for the recent_searches table URI.
String mimeType = mMockResolver.getType(SearchEntryProvider.CONTENT_URI);
assertEquals(SearchEntryProvider.CONTENT_TYPE, mimeType);
The mock ContentResolver sees the value of SearchEntryProvider.CONTENT_URI as:
url = {android.net.Uri$StringUri#831696969096}"content://com.eazyigz.provider.RussiaMediaSearch/searches"
The problem is that this assertEquals fails because it expects a CONTENT_TYPE of
vnd.android.cursor.dir
but instead receives
vnd.android.cursor.item
For the life of me, I cannot figure out how to get the mimeType to be vnd.android.cursor.dir. Anybody have experience with this?
Thanks,
Igor
For anyone who is interested, the problem was in my ContentProvider's implementation of getType method. I had to implement it like this to return the correct CONTENT_TYPE:
#Override
public String getType(Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match) {
// If the pattern is for searches, returns the general content type.
case SEARCHES:
return CONTENT_TYPE;
case SEARCH_ID:
return CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException(INVALID_URI + uri);
}
}
Related
I am developing a Weather app ,in that trying to build Uri that looks like
Content://com.example.weather.app/Location/locationName?Date=12012017
The documentation says Uri reference has pattern as ://?#
trying to understand this following code
public static Uri buildWeatherLocation(String locationSetting) {
return CONTENT_URI.buildUpon().appendPath(locationSetting).build();
}
public static Uri buildWeatherLocationWithStartDate(
String locationSetting, long startDate) {
long normalizedDate = normalizeDate(startDate);
return CONTENT_URI.buildUpon().appendPath(locationSetting)
.appendQueryParameter(COLUMN_DATE,Long.toString(normalizedDate)).build();
}
what is the actual difference and when we use appendPath() and appendQueryParameter() methods?
why can't we use appendQueryParameter() for locationSetting ,bit confusing suggestions plz
appendPath() is for path segments and appendQueryParameter() for query params with key value (in your example Date=12012017).
Check this link for more info and examples:
Use URI builder in Android or create URL with variables
appendQueryParameter is for query string parameters and appendPath is for site path
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 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.
Can anyone tell how can I create a ContentProvider which can query multiple database/ContentProviders for search suggestions provided by SearchView.
With ContentProviders, you are querying for data using a ContentUrl which would look something like this
content://<authority>/<data_type>/<id>
authority is the content provider name, e.g. contacts or for custom one will be com.xxxxx.yyy.
data_type and id are to specify what data you need from the provide and, if needed, a specific value for the key.
So, if you are building your custom content provider you need to parse the content uri which you get as a parameter in the query function and decide what data you need to return as Cursor. UriMatcher class is very good choice for this case. Here is an example
static final String URL = "content://com.mycompany.myapp/students";
static final Uri CONTENT_URI = Uri.parse(URL);
static final UriMatcher uriMatcher;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.mycompany.myapp", "students", 1);
uriMatcher.addURI("com.mycompany.myapp", "students/#", 2);
}
then in your query function, you would have something like this:
switch (uriMatcher.match(uri)) {
case 1:
// we are querying for all students
// return a cursor all students e.g. "SELECT * FROM students"
break;
case 2:
// we are querying for all students
// return a cursor for the student matching the given id (the last portion of uri)
// e.g. "SELECT * FROM students WHERE _id = n"
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
I hope this answers your question and direct you to the right track.
You can see a good article with full example about how to use them, here
http://www.tutorialspoint.com/android/android_content_providers.htm
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.