What Are The Semantics of Withvaluebackreference

What are the semantics of withValueBackReference?

This question relates to batch operation on a content provider. The example is modified from this related question.

When creating a batch of operations first create a list of operations to perform using:

ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();

then apply them to the content provider using the applyBatch method.

ContentProviderResult[] results = this.getContentResolver().applyBatch(FooBar.AUTHORITY, operations);

That is the basic concept so let's apply it. Suppose we have a content provider which handles uris for Foo records and some child records called Bar.

content://com.stackoverflow.foobar/foo

content://com.stackoverflow.foobar/foo/#/bar

For now we'll just insert 2 new Foo recordscalled "Foo A" and "Foo B", here's the example.

ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();

//add a new ContentProviderOperation - inserting a FOO record with a name and a decscription
operations.add(ContentProviderOperation.newInsert(intent.getData())
.withValue(FOO.NAME, "Foo A")
.withValue(FOO.DESCRIPTION, "A foo of impeccable nature")
.build());

//let's add another
operations.add(ContentProviderOperation.newInsert(intent.getData())
.withValue(FOO.NAME, "Foo B")
.withValue(FOO.DESCRIPTION, "A foo of despicable nature")
.build());

ContentProviderResult[] results = this.getContentResolver().applyBatch(FooBar.AUTHORITY, operations);

Nothing special here, we are adding 2 ContentProviderOperation items to our list and then applying the list to our content provider. The results array filled with the id's of the new records that we have just inserted.

So say we wanted to do something similar but we also want to add some child records into our content provider in one batch operation. We want to attach the child records to the Foo records we just created. The problem is we don't know the id of the parent Foo records because the batch has not been run. This is where the withValueBackReference helps us. Let's see an example:

ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();

//add a new ContentProviderOperation - inserting a Foo record with a name and a decscription
operations.add(ContentProviderOperation.newInsert(intent.getData())
.withValue(FOO.NAME, "Foo A")
.withValue(FOO.DESCRIPTION, "Foo of impeccable nature")
.build());

//let's add another
operations.add(ContentProviderOperation.newInsert(intent.getData())
.withValue(FOO.NAME, "Foo B")
.withValue(FOO.DESCRIPTION, "Foo of despicable nature")
.build());

//now add a Bar record called [Barbarella] and relate it to [Foo A]
operations.add(ContentProviderOperation.newInsert(intent.getData()
.buildUpon()
.appendPath("#") /* We don't know this yet */
.appendPath("bar")
.build())
.withValueBackReference (BAR.FOO_ID, 0) /* Index is 0 because Foo A is the first operation in the array*/
.withValue(BAR.NAME, "Barbarella")
.withValue(BAR.GENDER, "female")
.build());

//add a Bar record called [Barbarian] and relate it to [Foo B]
operations.add(ContentProviderOperation.newInsert(intent.getData()
.buildUpon()
.appendPath("#") /* We don't know this yet */
.appendPath("bar")
.build())
.withValueBackReference (BAR.FOO_ID, 1) /* Index of parent Foo B is 1*/
.withValue(BAR.NAME, "Barbarian")
.withValue(BAR.GENDER, "male")
.build());

ContentProviderResult[] results = this.getContentResolver().applyBatch(FooBar.AUTHORITY, operations);

So the withValueBackReference() method allows us to insert the related records before we know the id of the parent we want to relate them to. The back reference index is simply the index of the operation that will return the id we want to look for. It is perhaps easier to think of it in terms of which result you would expect to contain the id. e.g results[1] would contain the id for "Foo B" so the index we use to back reference to "Foo B" is 1.

What are the semantics of each InputType constant?

You can find this out by inspecting the source of the *KeyListener classes, though of course this may be changed in different versions or by manufacturer or carrier customizations. The below is based on the AOSP 4.3 source. These are only the effects that each type has on Android itself: input methods also use the types as hints to better predict what the user is likely to type. For example, although TYPE_TEXT_VARIATION_PERSON_NAME only has the effect of disabling spell-check, the IM might respond to this type by auto-completing from a dictionary of common names instead of from a language dictionary.

To experiment with input types and IME options, I hacked up a quick app that lets you select them from a list in a GUI, so you don't have to edit an XML layout and rebuild an app to do it. If you want to find out more, or check how they interact with a given IM app, download IM prove free from Google Play.

TYPE_NULL

This one is actually documented:

This should be interpreted to mean that the target input connection is not rich, it can not process and show things like candidate text nor retrieve the current text, so the input method will need to run in a limited "generate key events" mode, if it supports it. Note that some input methods may not support it, for example a voice-based input method will likely not be able to generate key events even if this flag is set.

This makes it sounds like it's for cases where you're not editing text, but pressing a key on the IM will do some action directly. But in fact it hides the IM completely. From the app's point of view, you almost never want this: set it if you only want a hardware keyboard to be able to enter text.

From the IM's point of view, you'll get this type passed to onStartInput a lot, usually when Android is about to hide the IM because a different activity is coming to the foreground. You want responding to this input type to be fast. There could be two reasons why it works this way, but someone involved with the design would have to confirm why:-

  1. It could be to let the IM know that editing is completely finished in that window (unlike onFinishInput, which merely means the IM is being hidden), so it can free memory used for dictionaries and the like until editing restarts.

  2. It could be part of what allows a hardware keyboard to use arrow keys for scrolling, menu accelerator keys, and so on, even when no text input is taking place.

Classes

Numeric types

TYPE_CLASS_NUMBER gives you the digits 0-9. In addition, adding TYPE_NUMBER_FLAG_SIGNED lets you have + or -, but only as the first character. Adding TYPE_NUMBER_FLAG_DECIMAL lets you have . in any position, but only once. You can have both signed and decimal. As far as I can tell, this isn't localized, so the allowed characters are the same even for locales where . is the thousands separator and , is the decimal point, or for locales with different number characters.

TYPE_CLASS_PHONE lets you have the digits 0-9, as well as any of #*+-(),/N.; and space. You can have those characters in any order and any number of times: there's no formatting check.

TYPE_CLASS_DATETIME | TYPE_DATETIME_VARIATION_DATE lets you have the digits 0-9 as well as any of /-.. Again, there's no extra check for formatting, so you can have them in any order.

TYPE_CLASS_DATETIME | TYPE_DATETIME_VARIATION_TIME lets you have the digits 0-9 as well as : and any of amp (for writing "am" or "pm", but you can use them in any order and position). Slightly perversely, you can't have space or . for "3 pm" or "2 p.m." or even "2.30". Again, it doesn't seem to be localized.

TYPE_CLASS_DATETIME | TYPE_DATETIME_VARIATION_NORMAL gets you 0-9 as well as :/-, space, and amp. This notably doesn't include . even though it's allowed in a date.

Based on the above observations, I can't say I'd recommend using any of the above classes. They all seem to have major absences and prevent localization. The above are the only classes with character restrictions.

TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD does as you'd expect: it uses a TransformationMethod to obscure the typed characters.

Text types

In TYPE_CLASS_TEXT, setting TYPE_TEXT_VARIATION_EMAIL_ADDRESS or TYPE_TEXT_VARIATION_EMAIL_SUBJECT makes a press of the enter key move focus to the next field instead of inserting a newline.

TYPE_TEXT_VARIATION_FILTER will prevent the input method going to full-screen (extract) mode.

TYPE_TEXT_VARIATION_PASSWORD has the obvious effect: it uses a TransformationMethod to obscure the typed characters. TYPE_TEXT_VARIATION_VISIBLE_PASSWORD still uses the TransformationMethod to prevent the text being copied

All of the following text variations allow spell-checking if TYPE_TEXT_FLAG_NO_SUGGESTIONS is not set. That is, using an class that is not TYPE_CLASS_TEXT or a variation that is not in this list has the same effect as setting TYPE_TEXT_FLAG_NO_SUGGESTIONS (which is described later).

  • TYPE_TEXT_VARIATION_NORMAL
  • TYPE_TEXT_VARIATION_EMAIL_SUBJECT
  • TYPE_TEXT_VARIATION_LONG_MESSAGE
  • TYPE_TEXT_VARIATION_SHORT_MESSAGE
  • TYPE_TEXT_VARIATION_WEB_EDIT_TEXT

Flags

The presence or absence of InputType.TYPE_TEXT_FLAG_MULTI_LINE has non-obvious side-effects. If the type class is not TYPE_CLASS_TEXT, it is always as if the flag were not set, and the TextView goes into single-line mode. Setting lines or maxLines to 1 only affects the way the text is displayed: it does not start single-line mode.

In single-line mode:-

  • the ellipsize option defaults to end
  • pressing enter performs the "editor action" or moves focus to the next field (just as for email addresses or subjects, above); otherwise, it inserts a newline
  • pressing tab moves focus to the next field, only if TYPE_TEXT_FLAG_IME_MULTI_LINE is not set; otherwise it inserts a tab character
  • the imeOptions can include an "editor action" to replace the enter key on a soft keyboard; in multi-line mode, TextView will add IME_FLAG_NO_ENTER_ACTION to the imeOptions
  • maxLines is automatically set to 1
  • adding a newline to the field (e.g. using setText) has no effect, and a carriage return is replaced with a zero-width space (U+FEFF)

TYPE_TEXT_FLAG_CAP_* use TextUtils.getCapsMode to decide whether to capitalize each character. The rules are a little baroque and are not locale-sensitive. AFAICT, this only takes effect if the corresponding setting is enabled in the default keyboard.

When TYPE_TEXT_FLAG_AUTO_CORRECT is set, space, tab, newline, any Unicode "end punctuation" character, or any of ,.!?" will trigger auto-correction of the preceding word (in this context, that's the longest sequence of Unicode letters and apostrophes). If the whole word doesn't have a correction, it continues to retry with shorter subsequences. The corrections come from a fixed system resource com.android.internal.R.xml.autotext and are separate from any configured spell-checker.

TYPE_TEXT_FLAG_NO_SUGGESTIONS (or the text variations listed earlier) stops the text being spell-checked. This prevents the spell-check suggestions list being shown, and also stops misspelled words being highlighted. The input method can still provide completions if it chooses.

Other oddities

Long-tapping a TextView usually selects the tapped word, but if the input type is one of the following, it selects all of the text instead:-

  • TYPE_CLASS_NUMBER
  • TYPE_CLASS_PHONE
  • TYPE_CLASS_DATETIME (any variation)
  • TYPE_TEXT_VARIATION_URI
  • TYPE_TEXT_VARIATION_EMAIL_ADDRESS
  • TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS
  • TYPE_TEXT_VARIATION_FILTER

Android ContentProvider applyBatch not saving all contact data

It's actually just a tiny but important typo! You use withValueBackreference() only for those types which function - you use withValue() for those others. No wonder, those do not work. Just use withValueBackReference() here as well and all will work as expected!

Android: SQLite transactions when using ContentResolver

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.

Android: SQLite one-to-many design

I think you're looking at the wrong end of the one-to-many relationship.

Take a look at the ContactsContract content provider, for example. Contacts can have many email addresses, many phone numbers, etc. The way that is accomplished is by doing inserts/updates/deletes on the "many" side. To add a new phone number, you insert a new phone number, providing an ID of the contact for whom the phone number pertains.

You would do the same if you had a plain SQLite database with no content provider. One-to-many relationships in relational databases are achieved via inserts/updates/deletes on a table for the "many" side, each having a foreign key back to the "one" side.

Now, from an OO standpoint, this isn't ideal. You are welcome to create ORM-style wrapper objects (think Hibernate) that allow you to manipulate a collection of children from the "one" side. A sufficiently-intelligent collection class can then turn around and synchronize the "many" table to match. However, these aren't necessarily trivial to implement properly.

Android: how can I lock SQLite database accessible with ContentProvider (or other way of executing atomic conditional operations)

The answer turned out to be pretty simple, but it is kind of a "hack" - just add additional Uri to ContentProvider.

For example: initially my ContentProvider supported the following URIs:

  • Uri.withAppendedPath(MyContract.CONTENT_URI, "entities")
  • Uri.withAppendedPath(MyContract.CONTENT_URI, "user_actions")

In order to support the atomic operation described in the question I added an additional Uri:

  • Uri.withAppendedPath(MyContract.CONTENT_URI, "clear_modified_flag")

When this Uri is updated through:

getContentResolver().update(
MyContract.ClearModifiedFlag.CONTENT_URI,
new ContentValues(),
null,
null);

my ContentProvider executes an SQLite transaction that locks the database for the duration of the operation and rolls it back in case of any errors (as described in this answer).

That's it.

P.S. my ContentProvider is not exported (i.e. other apps can't access and use it), therefore it is safe to add this new Uri to it. But keep in mind that if you do export your ContentProvider, then exposing functionality like this one could be problematic.



Related Topics



Leave a reply



Submit