UriMatcher does not match a pattern - android

I am creating a content provider for an android application, but I am having a problem correctly matching the uri using UriMatacher.
For example, I add the uri's to match (snipped from the link)
sURIMatcher.addURI("content://com.example", "people", PEOPLE);
sURIMatcher.addURI("content://com.example", "people/#", PEOPLE_ID);
sURIMatcher.addURI("content://com.example", "people/#/phones", PEOPLE_PHONES);
And then attempt to access contacts/people/1/phones. The successful match ends up being with PEOPLE_ID instead of PEOPLE_PHONES.
The query is initially generated by this code.
Uri uri = Uri.parse("content://com.example/people/#/phones");
ContentUris.appendId(uri.buildUpon(), 1).build();
With some logging statements thrown in, I see that the following:
The uri passed to the query gives this:
content://com.example/people/1#/phones
but uri.getPath() gives this:
/people/1
The third path part of the uri is clearly dropped, which explains why it was matching the wrong uri.
The example from the Android developer website seems to indicate that there shouldn't be a problem with this. Am I creating the uri incorrectly? Is it just a bug? Is this intended functionality (and therefore the example from android developers is a bad one)?

Uri.parse() is ignorant of the UriMatcher's wildcards; here, the # is the fragment identifier of a URI, so when you parse content://com.example/people/#/phones, it becomes content://com.example/people + fragment /phones. The id is correctly appended to the end of the URI, and then the fragment is carried over. In this case, you can't rely on ContentUris, but rather need to build the Uri the long way:
path = new Uri.Builder()
.scheme( ContentResolver.SCHEME_CONTENT )
.authority( DataProvider.AUTHORITY )
.appendPath( "people" )
.appendPath( "1" )
.appendPath( "phones" ) ).build();

Related

How to cast String to Input<String> - Kotlin

I am using GraphQL Apollo client call and it generates files. So as a result I got this
val storeNumber: Input<String> = Input.absent()
Instead of regular string. So how can I cast parameter to Input<String> to avoid this error
I don't use Apollo, but found the source code of Input. It depends what version of this library you're using. If you're using an older version, to wrap (not "cast"!) a String as an Input, use Input.Present:
storeNumber = Input.Present(storeNumber)
Note, the term "cast" means promising the compiler that your existing instance is also an instance of something else. That is very different from converting or wrapping an instance of something.
If you're using a newer version of the library, you shouldn't be using the Input class at all. It's been replaced with the Optional class, in which case you would use Optional.Present(storeNumber).
If you want to figure this kind of thing out on your own in the future, try Ctrl+Clicking the function you're working with to jump to its source code. In turn you can Ctrl+Click the types of the function parameters. That would take you to the source code of Input so you could see how to create an instance.

Kotlin - Why is this variable null after initialization?

I'm trying to write a unit test for some Android code that looks for a specific key being present in an Intent object. As part of my test, I'm creating an Intent object and adding a String to it. However, when I use the below code, my variable is initalized to null:
val data = Intent().putExtra("key", "value")
// data is null
If I split this up into two lines, it works just fine:
val data = Intent()
data.putExtra("key", "value")
// data is non-null and contains my key/value
What feature of the Kotlin language is causing this to happen?
Note that putExtra() returns an Intent object. From the Android source:
public #NonNull Intent putExtra(String name, String value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putString(name, value);
return this;
}
In the first case, the inferred type is Intent!. I was under the impression that this just means that it's an Intent or an Intent? but Kotlin doesn't want to make devs go crazy with Java platform types. Still, given that putExtra() returns a non-null value, I'd expect the actual value of data to be non-null.
The short answer is what #CommonsWare and #TheWanderer mentioned in comments: my test class was in the test/ directory, so it was using a mock Intent implementation instead of the real thing.
When I move my test to the androidTest/ directory, everything works as expected. The observed behavior has nothing to do with Kotlin.
Some extra info about why this was so confusing...
First, I was mistaken when I wrote this:
val data = Intent()
data.putExtra("key", "value")
// data is non-null and contains my key/value
The data variable was non-null, but it did not actually contain my key/value pair. The mock Intent implementation I was using was dropping the putExtra() call.
So, why was my test passing?
The one particular test I decided to dig deeper on was testing the negative case (when a key other than the one it expects is present in the Intent). But I wasn't passing an Intent with the wrong key, I was passing an Intent with no keys at all. Either way, though, the expected key is not present, and the method returns false.
The positive case (where the required key actually was passed to putExtra()) failed with an AssertionError. Too bad I didn't pick this one to scrutinze.
My main project has apparently stubbed Intent.putExtra() as a no-op, via the returnDefaultValues = true gradle option. When I create a new project and try to reproduce this issue, I get a very clear error:
java.lang.RuntimeException: Method putExtra in android.content.Intent not mocked. See http://g.co/androidstudio/not-mocked for details.
at android.content.Intent.putExtra(Intent.java)
at com.example.stackoverflow.IntentTest.test(IntentTest.kt:12)
Unfortunately, with the mocked putExtra(), I never got this helpful message.

Difference between URI and Uri in android?

I am newbie to Android development and I am confused between the difference of URI and Uri?
Please mention the main differences.
java.net.URI is mutable
android.net.Uri is immutable
Immutable URI reference. A URI reference includes a URI and a fragment, the component of the URI following a '#'. Builds and parses URI references which conform to RFC 2396.
In the interest of performance, this class performs little to no validation. Behavior is undefined for invalid input. This class is very forgiving--in the face of invalid input, it will return garbage rather than throw an exception unless otherwise specified.
More infos here

ContentObserver is called even when there are no changes in the cursor

I have a content observer that should be notified when one of the contacts added by my sync adapter is modified.
I register and unregister the observer doing this:
private static final Uri MYAPP_CONTENT_URI = ContactsContract.RawContacts.CONTENT_URI.buildUpon().appendQueryParameter(RawContacts.ACCOUNT_NAME, SyncAdapter.MYAPP_ACCOUNT_NAME).appendQueryParameter(RawContacts.ACCOUNT_TYPE, MY_APP_ACCOUNT_TYPE).build();
public static void registerContentObserver() {
ContentResolver resolver = MyApplication.getAppContext().getContentResolver();
cursorContacts = resolver.query(MYAPP_CONTENT_URI, null, RawContacts.DELETED + "=0", null, null);
cursorContacts.registerContentObserver(MYAPP_URI_OBSERVER);
}
public static void unregisterContentObserver() {
if (cursorContacts != null) {
cursorContacts.unregisterContentObserver(MYAPP_URI_OBSERVER);
cursorContacts.close();
}
}
The problem is that even when the cursor is empty (getCount returns 0) after I register the observer I get a call to onChange what ever I do in the native address book.
Shoudn't the observer be called only when one of the entries in the cursor was modified?
The documentation states:
Register an observer that is called when changes happen to the content backing this cursor
What's "the content that is backing this cursor"? I thought it was the list of lookupuri of the contacts in the cursor but it looks like it is enough to have a change in the ContactsContract.RawContacts.CONTENT_URI.
I have also tried to register one observer for each Uri. It does not help. Although the documentation for ContentResolver.registerContentObserver states:
Register an observer class that gets callbacks when data identified by a given content URI changes.
Parameters
uri The URI to watch for changes. This can be a specific row URI, or a base URI for a whole class of content.
notifyForDescendents If true changes to URIs beginning with uri will also cause notifications to be sent. If false only changes to the exact URI specified by uri will cause notifications to be sent. If true, than any URI values at or below the specified URI will also trigger a match.
(I set notifyForDescendents to false but it shouldn't have called the observers in any case).
What's wrong?
Thank-you
It is up to the content provider to decide when to report changes. For complicated content providers (like the contacts provider) it can be very difficult to determine all of the specific URIs that change due to an operation, so they just report a global change when something happens.
Query parameters in your Uri, the Fragment, nor even the Scheme are considered when Observer Uri matching occurs. The only thing that matters is the Uri Authority and the Path Segments. Strict left to right matching occurs. I have not tested "*" in a path segment to denote a wildcard, but I suspect that it will not work.
Your particular Observer is ContactsContract.RawContacts.CONTENT_URI, so any time any contact content changes for any reason, your Observer will fire.

Creating & using ContentProvider for android

When I call an Android ContentProvider I get the following exception:
java.lang.RuntimeException: Unable to
start activity
ComponentInfo{de.harm.android.couchone/de.harm.android.couchone.CouchContactClient}:
java.lang.IllegalArgumentException:
Unknown URL
content://de.harm.android.couchone.provider/test2
These are the projects:
https://github.com/der-harm/CouchOneProvider
.../CouchOneContacts
Android uses the so-called ContentResolver to communicate with ContentProvider which in turn handles the persistence functionality - accessing the database.
The ContentProvider registers itself with a unique Uri. The ContentResolver calls the ContentProvider with this Uri and passes additional data, like a SQL query string and/or data to be saved.
In the CouchOneProvider/AndroidManifest.xml I have the following:
<provider android:authorities="de.harm.android.couchone.provider"
android:name=".Provider" />
The Provider uses
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, DB_Name, URI_COLLECTION);
uriMatcher.addURI(PROVIDER_NAME, DB_Name + "/#", URI_ENTITY);
}
and
public static boolean isCollectionUri(Uri uri) {
return uriMatcher.match(uri) == URI_COLLECTION;
}
to process the CONTENT_URI used by the ContentResolver to call the ContentProvider:
Am I missing permissions in any of both AndroidManifest.xml?
Am I defining the authority in AndroidManifest.xml wrongly?
Is the CONTENT_URI wrong?
Update:
I have additional information:
Logcat says:
Failed to find provider info for
de.harm.android.couchone.provider
This should be the starting point. But so far I couldn't find any solution.
The fully qualified classname of the ContentProvider implementation is:
de.harm.android.couchone.Provider
In AndroidManifext.xml exactly this is specified as authority, except for the name being to lower case, but this should be fine.
The package name is defined previously in the xml file, so ".Provider" should be ok, too.
As to be seen in the exception, the client calls:
content://de.harm.android.couchone.provider/test2
Logcats answer is:
Failed to find provider info for de.harm.android.couchone.provider
I don't see what's missing, perhaps it's Eclipse or emulator problem?
I install the provider as "run project as Android application".
I have resolved this problem:
Both projects had the same package structure. I changed de.harm.android.couchone to de.harm.android.couchone.provider and de.harm.android.couchone.client.
I think this link is related to question topic. How to implement a custom content-provider.

Categories

Resources