How to handle advanced Uri Patterns in Android - android

Implementation of Content Provider with a complicated queries required a complex uri patterns, and i don't understand how can i handle this.
Can i use Regular Expression in my patterns? example: /user/:[a-zA-Z]/timeline/ if the user must contain only letters.
Which symbols tells the UriMather what the parameters example: /user/:userId/timeline/year/:yearNumber, i would get userId and yearNumber as parameters , so how do i should get the values ? should i use getPathSegments() and get the parameters manually?
or if i used /user/#/timeline/year/# how do i extract the # values

By using the format /user/#/timeline/year/# you can call:
uri.getPathSegments().get(1); // To get the first #
uri.getPathSegments().get(4); // to get the second #
// 0 -> user, 1 -> first #, 2 -> timeline, 3 -> year, 4 -> second #
So in your content provider you would have something like this:
private static final int USER_TIMELINE_YEAR = 1;
// ...
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static
{
uriMatcher.addURI(PROVIDER_NAME, "user/#/timeline/year/#", USER_TIMELINE_YEAR);
}
// Usually a ContentProvider method like query, insert, delete and update
public void someMethod(Uri uri) {
if(uriMatcher.match(uri) == USER_TIMELINE_YEAR) {
String userId = uri.getPathSegments().get(1);
String timelineYear = uri.getPathSegments().get(4);
}
}
Check https://developer.android.com/guide/topics/providers/content-provider-creating.html and https://developer.android.com/reference/android/content/UriMatcher.html

Related

Supplement a Records Columns in Room

I am fairly new to Android Room and SQLite in general, so sorry if this is a simple question.
I am getting data from a API that I'd like to insert into a database so it's accessible when the device is offline.
Depending on the endpoint of the API, some fields of my Data objects may be null (Think a summary with just the basic fields versus a fully detailed object with all fields)
To keep the database clean, I'd like to update the entries, but only the columns that are not null (eg. that I have new values for) and keep the rest of the columns untouched.
Here are some example classes to clarify:
Person
#Entity(tableName = "person", indices = {
#Index(value = "id", unique = true)
})
public class Person {
#PrimaryKey
public int id;
public String name;
public String description;
}
Example:
// create db
RoomDB db = RoomDB.create(ctx);
// create some sample objects
final Person p2 = new Person(2, "Peter", null);
// insert them into the db
db.personDao().insert(p2);
// create a updated peter that likes spiders
// but has no name (as a example)
final Person newPeter = new Person(2, null, "Peter likes spiders");
// and update him
db.personDao().updateNonNull(newPeter);
// now we read him back
final Person peter = db.personDao().getById(2);
In this example, the desired values of 'peter' would be:
id = 2
name = "Peter"
description = "Peter likes spiders"
However, using Room's #Update or #Insert i can only get this:
id = 2
name = null
description = "Peter likes spiders"
The only way i found to achive this would be to manuall get the object and supplement the values like so:
#Transaction
public void updateNonNull(Person newPerson) {
final Person oldPerson = getById(newPerson.id);
if (oldPerson == null) {
insert(newPerson);
return;
}
if (newPerson.name == null)
newPerson.name = oldPerson.name;
if (newPerson.description == null)
newPerson.description = oldPerson.description;
update(newPerson);
}
However, that would result in quite a bit of code with bigger objects...
So my question, is there a better way to do this?
Edit:
After some Testing with the SQL by #Priyansh Kedia, i found that those functions indeed work as intended and do so at a higher performance than java.
However, as a SQL statement would have required me to write huge queries, i decided to use a Reflection based solution, as can be seen below.
I only did so because the function isn't called regularly, so the lower performance won't matter too much.
/**
* merge two objects fields using reflection.
* replaces null value fields in newObj with the value of that field in oldObj
* <p>
* assuming the following values:
* oldObj: {name: null, desc: "bar"}
* newObj: {name: "foo", desc: null}
* <p>
* results in the "sum" of both objects: {name: "foo", desc: "bar"}
*
* #param type the type of the two objects to merge
* #param oldObj the old object
* #param newObj the new object. after the function, this is the merged object
* #param <T> the type
* #implNote This function uses reflection, and thus is quite slow.
* The fastest way of doing this would be to use SQLs' ifnull or coalesce (about 35% faster), but that would involve manually writing a expression for EVERY field.
* That is a lot of extra code which i'm not willing to write...
* Besides, as long as this function isn't called too often, it doesn't really matter anyway
*/
public static <T> void merge(#NonNull Class<T> type, #NonNull T oldObj, #NonNull T newObj) {
// loop through each field that is accessible in the target type
for (Field f : type.getFields()) {
// get field modifiers
final int mod = f.getModifiers();
// check this field is not status and not final
if (!Modifier.isStatic(mod)
&& !Modifier.isFinal(mod)) {
// try to merge
// get values of both the old and new object
// if the new object has a null value, set the value of the new object to that of the old object
// otherwise, keep the new value
try {
final Object oldVal = f.get(oldObj);
final Object newVal = f.get(newObj);
if (newVal == null)
f.set(newObj, oldVal);
} catch (IllegalAccessException e) {
Log.e("Tenshi", "IllegalAccess in merge: " + e.toString());
e.printStackTrace();
}
}
}
}
There is no in-built method in room to do this
What you can do is, put check in the query for your update method.
#Query("UPDATE person SET name = (CASE WHEN :name IS NOT NULL THEN :name ELSE name END), description = (CASE WHEN :description IS NOT NULL THEN :description ELSE description END) WHERE id = :id")
Person update(id: Int, name: String, description: String)
We have written the update query for SQL which checks if the inserted values are null or not, and if they are null, then the previous values are retained.

Android Annotation switch between different datatype

Can annotation in Android receive two different data type or do the conversion internally? So that it can read both data type. For example a usual annotation:
#StringDef({CheckInReward.NEW, CheckInReward.USED})
#Retention(RetentionPolicy.SOURCE)
public #interface CheckInReward{
String NEW = "NEW";
String USED = "USED"; }
So let say if backend pass me this status in integer format (new = 1, used = 2). But I don't want to create a new annotation file, can I reuse this file but tweak something inside? like
#StringDef({CheckInReward.NEW, CheckInReward.USED})
#IntDef({CheckInReward.new, CheckInReward.used})
#Retention(RetentionPolicy.SOURCE)
public #interface CheckInReward{
String NEW = "NEW";
String USED = "USED";
int new = 1;
int used = 2; }
I don't know is there any similar question but as my searching doesn't show any result. I know this is not the second piece of code is not working. Just an example defining my question.

how to fetch record by pagination using Fetch query and CRMSDK2015 from MS CRM to Android?

by using sdk and fetch query i got 5000 records in one request and it takes too much time for fetch.
please give me suggestion how to get records in page by page using Fetch query and sdk in Android.
Not sure about the Android SDK bit but FetchXML Paging is covered here: Page large result sets with FetchXML
You can try with Linq. Have a look here -
https://msdn.microsoft.com/en-us/library/gg334684.aspx?f=255&MSPPError=-2147217396
You can try this in Query Expression as well:
Page large result sets with QueryExpression
// Query using the paging cookie.
// Define the paging attributes.
// The number of records per page to retrieve.
int queryCount = 3;
// Initialize the page number.
int pageNumber = 1;
// Initialize the number of records.
int recordCount = 0;
// Define the condition expression for retrieving records.
ConditionExpression pagecondition = new ConditionExpression();
pagecondition.AttributeName = "parentaccountid";
pagecondition.Operator = ConditionOperator.Equal;
pagecondition.Values.Add(_parentAccountId);
// Define the order expression to retrieve the records.
OrderExpression order = new OrderExpression();
order.AttributeName = "name";
order.OrderType = OrderType.Ascending;
// Create the query expression and add condition.
QueryExpression pagequery = new QueryExpression();
pagequery.EntityName = "account";
pagequery.Criteria.AddCondition(pagecondition);
pagequery.Orders.Add(order);
pagequery.ColumnSet.AddColumns("name", "emailaddress1");
// Assign the pageinfo properties to the query expression.
pagequery.PageInfo = new PagingInfo();
pagequery.PageInfo.Count = queryCount;
pagequery.PageInfo.PageNumber = pageNumber;
// The current paging cookie. When retrieving the first page,
// pagingCookie should be null.
pagequery.PageInfo.PagingCookie = null;
Console.WriteLine("Retrieving sample account records in pages...\n");
Console.WriteLine("#\tAccount Name\t\tEmail Address");
while (true)
{
// Retrieve the page.
EntityCollection results = _serviceProxy.RetrieveMultiple(pagequery);
if (results.Entities != null)
{
// Retrieve all records from the result set.
foreach (Account acct in results.Entities)
{
Console.WriteLine("{0}.\t{1}\t{2}", ++recordCount, acct.Name,
acct.EMailAddress1);
}
}
// Check for more records, if it returns true.
if (results.MoreRecords)
{
Console.WriteLine("\n****************\nPage number {0}\n****************", pagequery.PageInfo.PageNumber);
Console.WriteLine("#\tAccount Name\t\tEmail Address");
// Increment the page number to retrieve the next page.
pagequery.PageInfo.PageNumber++;
// Set the paging cookie to the paging cookie returned from current results.
pagequery.PageInfo.PagingCookie = results.PagingCookie;
}
else
{
// If no more records are in the result nodes, exit the loop.
break;
}
}

ORMLite alias in rawQuery

Is it possible to use an alias (AS) in a query for ORMLite in Android? I am trying to use it with the following code:
String query =
"SELECT *, (duration - elapsed) AS remaining FROM KitchenTimer ORDER BY remaining";
GenericRawResults<KitchenTimer> rawResults =
getHelper().getKitchenTimerDao().queryRaw(
query, getHelper().getKitchenTimerDao().getRawRowMapper());
But when this codes gets executed it gives the following error:
java.lang.IllegalArgumentException: Unknown column name 'remaining' in table kitchentimer
java.lang.IllegalArgumentException: Unknown column name 'remaining' in table kitchentimer
The raw-row-mapper associated with your KitchenTimerDao expects the results to correspond directly with the KitchenTimer entity columns. However, since you are adding your remaining column, it doesn't no where to put that result column, hence the exception. This is a raw-query so you will need to come up with your own results mapper -- you can't use the DAO's. See the docs on raw queries.
For instance, if you want to map the results into your own object Foo then you could do something like:
String query =
"SELECT *, (duration - elapsed) AS remaining FROM KitchenTimer ORDER BY remaining";
GenericRawResults<Foo> rawResults =
orderDao.queryRaw(query, new RawRowMapper<Foo>() {
public Foo mapRow(String[] columnNames, String[] resultColumns) {
// assuming 0th field is the * and 1st field is remaining
return new Foo(resultColumns[0], Integer.parseInt(resultColumns[1]));
}
});
// page through the results
for (Foo foo : rawResults) {
System.out.println("Name " + foo.name + " has " + foo.remaining + " remaining seconds");
}
rawResults.close();
I had the same problem. I wanted to get a list of objects but adding a new attribute with an alias.
To continue using the object mapper from OrmLite I used a RawRowMapper to receive columns and results. But instead of convert all columns manually I read the alias first and remove its reference in the column arrays. Then it is possible to use the OrmLite Dao mapper.
I write it in Kotlin code:
val rawResults = dao.queryRaw<Foo>(sql, RawRowMapper { columnNames, resultColumns ->
// convert array to list
val listNames = columnNames.toMutableList()
val listResults = resultColumns.toMutableList()
// get the index of the column not included in dao
val index = listNames.indexOf(ALIAS)
if (index == -1) {
// There is an error in the request because Alias was not received
return#RawRowMapper Foo()
}
// save the result
val aliasValue = listResults[index]
// remove the name and column
listNames.removeAt(index)
listResults.removeAt(index)
// map row
val foo = dao.rawRowMapper.mapRow(
listNames.toTypedArray(),
listResults.toTypedArray()
) as Foo
// add alias value. In my case I save it in the same object
// but another way is to create outside of mapping a list and
// add this value in the list if you don't want value and object together
foo.aliasValue = aliasValue
// return the generated object
return#RawRowMapper foo
})
It is not the shortest solution but for me it is very important to keep using the same mappers. It avoid errors when an attribute is added to a table and you don't remember to update the mapping.

ContentProvider for multiple databases/ContentProviders

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

Categories

Resources