Call API in content provider for global search - android

We are attempting to hook up our AndroidTV app to append results into the global search. I'm running into an issue where I cannot make an api call to get the results because the system calls my content provider on the main thread.
#Override
public Cursor query(Uri uri, String[] projection, String search, String[] selectionArgs, String searchOrder) {
... Logic here that calls the API using RxJava / Retrofit
return cursor;
}
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="#string/foo"
android:searchSettingsDescription="#string/foo_results"
android:includeInGlobalSearch="true"
android:searchSuggestAuthority="com.foo.search.provider"
android:searchSuggestIntentAction="android.intent.action.VIEW" />
<provider
android:authorities="com.foo.search.provider"
android:name=".search.GlobalSearchProvider"
android:exported="true"/>
When i do a global search i can see that the ContentProvider#query is called. If i attempt to do an api call on the current thread i get an networkonmainthreadexception.
I have attempted to notifty the cursor that data has changed via but had no success either.
getContext().getContentResolver().notifyChange(Uri.parse("content://com.foo.test"), null);
...
cursor.setNotificationUri(getContext().getContentResolver(), Uri.parse("content://com.foo.test"));
Is there anyway i can force the O.S to call the content provider on a seperate thread or at least notify the search that the cursor has new content?
Thank You

One of the solutions can be to set the content provider process
android:process:":androidtv"
and set the ThreadPolicy to LAX just before making network call
ThreadPolicy tp = ThreadPolicy.LAX;
StrictMode.setThreadPolicy(tp);
By running contentprovider in a different process, even if the query runs on main thread, it will not affect your UI operations

I've also struggled with this as I didn't find the currently accepted answer of blocking the UI acceptable.
However, according to Marc Bächinger from the Google TV team, this is only a problem with the emulator. In more recent builds (such as those in the currently available hardware) the search providers are called in a background thread, which avoid the problem altogether.
I've been able to test it on the Nexus Player and can confirm it works properly.
Source: https://plus.google.com/+DanielCachapa/posts/dbNMoyoRGEi

EDITED ANSWER
I've experienced this issue myself and I had to rely on the accepted answer's proposed solution. However, I've noticed that there is a noticeable lag when typing in the 'Global search' box. This lag is:
Caused by the application, since its removal makes the lag disappear
Most likely due to a synchronous wait on the queried applications - since our app does two network requests, the query() method takes time to complete, resulting in this lag
I found out that the separate process (:androidtv) is not necessary. By setting the ThreadPolicy.LAX configuration, the network request will still execute without throwing a NetworkOnMainThreadException.
I still don't understand why the lag is there.
ORIGINAL ANSWER
I don't believe that the accepted answer, while it certainly works, is the right way do it.
Once the query() method is called, you should spawn a new thread/task/job to perform a network call (therefore, avoiding the NetworkOnMainThreadException), which will update the adapter once it obtains the desired data.
There are different ways of doing this. You can either use a callback or an event bus (e.g., Otto). This is the method that I call to update the adapter:
public void updateSearchResult(ArrayList<Data> result) {
mListRowAdapter.clear();
mListRowAdapter.addAll(0, result);
HeaderItem header = new HeaderItem(0, "Search results", null);
mRowsAdapter.add(new ListRow(header, mListRowAdapter));
}

In order to solve for displaying the result in the global search using API in query method what I basically did is introduce a delay between fetching of api result and querying the db for results to return a cursor.
You can do this via
private Cursor getSuggestions(final String query) {
Cursor cursor;
cursor = getCursor(query);
if (cursor==null || cursor.getCount() == 0) {
//apiCall
try {
Thread.sleep(X millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
cursor = getCursor(query);
}
return cursor;
}
Will keep on looking to see if, we can get some kind of hook to reattach without using a delay.

Because I wasn't really familiar with the Android thread. For anyone who has the same problem as me. The main point is that the query() method in content provider is not running on the UI thread.
Instead of using the asynchronous function to do HTTP requests and then update the cursor, please simply use the synchronous function to do HTTP requests and then return the cursor with the data you want.

Related

How to change this piece of code to receive result from a Thread operation?

I have this piece of code running on Thread, for Android device. It was initially sufficient, but now I have a change in requirements that requires me to return the result after a HTTP POST. The "POST" function is in another class, so the code below will not work.
I read that RunnableFuture allows extraction of results for return of result. Can someone guide me on how to change this piece of code to that?
public ArrayList<String> addfood(final String status, final String spaceId) {
Log.d(TAG, "Add Food");
final ArrayList<String> results = new ArrayList<>();
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
try {
String cookie = android.webkit.CookieManager.getInstance().getCookie(sURL);
Uri.Builder builder = new Uri.Builder()
.appendQueryParameter(“status”,status)
.appendQueryParameter("cookie", cookie)
.appendQueryParameter("space", spaceId);
String addresult = foodpost(POST(url, builder));
results.add(addresult);
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
return results;
}
let me clarify you a pair of things:
Analyzing your piece of code you're trying to access to another method of other class (as you said in your comment) from inside of one thread:
The "POST" function is in another class, so the code below will not work
It doesn't work because you have to have a reference to the other object in order to use the method.
But let me ask you a couple of things:
Concurrency is hard (very hard). Even if after modifying your piece of code to use properly the POST method, have you ever considered what happens with that thread? What happens when the user decides to rotate the device? What happens when your UiThread (the thread responsible of updating all UI elements of your application) decides to read that list of resutls (is thread safe also?)? I mean there are a lot of considerations to take care before creating a thread to do some background operation. And trust me, is very easy to do things wrong. Maybe it can leak memory and your app may become an unresponsive one. Do you think is neccesary?
There are a lot of libraries that can help you to achieve that network request in a easy manner, carrying the weight of doing background things for you. Those libraries are very battle tested, it has a decent amount of user base. Libraries like Retrofit or Volley are very good options to do whatever thing you want to achieve. So is there a need for not using that?
Sorry if I discourage you to use the thread, but there are a lot of useful options that would help you in the long run.

Race conditions in Cursors for Android?

So I have this app that loads data via a provider into a cursor.
The cursor is then used to fetch the data for the app to use.
The loading process is done with the LoaderManager API.
Now the problem arises after I call swapCursor() from onLoadFinished() in some circumstances; the cursor reports having the correct amount of data, but when I try to access it I get a CursorIndexOutOfBoundsException.
The peculiar thing is, I know exactly which line this occurs on, so I put a breakpoint there that suspends all threads and tried to inspect the data to see what was up, suddenly the error disappears.
Now by trial and error I figured that the problem goes away even if I do nothing. Like literally nothing after it suspends everything. If I just wait for a few seconds before resuming execution the data is perfectly accessible.
So my question is: Does anybody know if the Android operating system does something in the background that creates a race condition? I don't really see how this can be a bug on my side since when I tested it I suspended all threads, so my app can't do anything while I wait for the issue to resolve.
As a sidenote, if I just replace the swapCursor() call with changeCursor() (doing this means I have to use restartLoader() instead of initLoader() due to some other feature of the system) the bug goes away, but I've read somewhere that this is not the proper way to do it, since it doesn't handle the closing of the cursor properly (I think this was the reason I had to use restartLoader(), but I can't really recall at the moment).
Some code excerpts:
#Override
public void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
...
getLoaderManager().initLoader( ACTIVE_TEAM_LOADER, null, this );
}
...
#Override
public void onLoadFinished( Loader<Cursor> loader, Cursor data ) {
switch ( loader.getId() ) {
case ACTIVE_TEAM_LOADER:
activeTeamAdapter.swapCursor(
new MergeCursor( new Cursor[]{ data, createNewCursor } ) );
...
}
And the offending line, called deep in the stack from a CursorAdapter.bindView():
long value = data.getLong( data.getColumnIndex( getDBName() ) );
To me it looks like the data in the cursor is not properly loaded yet.
The problem was caused by the fact that swapCursor() does not close the previous MergeCursor cursor. Closing the previous cursor solved the problem, but introduced a new problem because you are not supposed to close cursors from onLoadFinished(), and when closing a MergeCursor the MergeCursor closes all sub-cursors as well.
This was solved by switching back to using changeCursor() whenever I use adapters to only display MergeCursors (some of the adapters display MergeCursors and MatrixCursors when there is no data from onLoadFinished() and for some reason MatrixCursors seems like they need to be closed between usages so this does not apply to them, I don't know why) and wrapping any cursors in a class that disregards closing.
onLoadFinished() excerpt:
Cursor[] cursors = new Cursor[] { new UnclosableCursor( data ), createNewCursor };
activeTeamAdapter.changeCursor( new MergeCursor( cursors ) );
The wrapper:
public class UnclosableCursor extends CursorWrapper {
public UnclosableCursor( Cursor cursor ) {
super( cursor );
}
#Override
public void close() {}
}
A previous version of this answer alluded to the fact that this had to do with casting, but after more research this seems to only have been a symptom of the problem, not the actual problem.

Query efficiency with Parse using local store in Android

My question is very simple, what is the best approach to work with Parse using the local store at the time I want to query the saved objects.
Is it better to trigger several queries to the local store directly on the main thread and avoid nesting a lot of anonymous classes or using a background thread?
It's important thing to notice is that this method is going to be called very frequently and the pattern will be repeated in several places with different queries. I'm evaluating both efficiency and code quality in readability. These methods will be called synchronously so we can assume the data will be consistent at any time.
As the objects are being saved locally I would expect the queries to be very fast in response. Here's a rough sample of how the code would look like in both cases.
Option one:
public void processBatches() {
ParseQuery<Batch> batchQuery = Batch.getQuery();
int batchCount = batchQuery.fromLocalDatastore().count();
List<Batch> batches = batchQuery.fromLocalDatastore().find();
for(Batch b : batches) {
// do whatever I need to do
}
}
Option two:
public void processBatches() {
ParseQuery<Batch> batchQuery = Batch.getQuery();
int batchCount = batchQuery.fromLocalDatastore().countInBackground(new CountCallback() {
#Override
public void done(int i, ParseException e) {
if (i > 0) {
batchQuery.findInBackground(new FindCallback<Batch>() {
#Override
public void done(List<Batch> list, ParseException e) {
for (Batch batch : list) {
// do whatever I need to do
}
}
});
}
}
});
}
Well since in option one you are blocking the UI thread, there could be a delay in the user's ability to interact with your application. This is not a very good option since even if it is for just a moment, users don't want to be waiting unless they know operations are happening. But, if you know that at any time there will be little to no delay, go ahead and do it.
Nevertheless, I argue that option two is going to be the best option. This is because, in general, all network operations should be performed in the background. Although in your case you are performing local datastore queries, suppose a user has gone to their application task manager and cleared the data (very rare this will happen) what happens now when you perform the find from local data store and processing of Batch objects? Well, the app crashes. Again, this is not a very good option for the usability for your application.
Choose the second option, and allow an AsyncThread to run the find() and count() query operations to the network if there is nothing found from local data store queries. Also, from the Parse documentation for find:
public Task<List<T>> findInBackground()
Retrieves a list of ParseObjects that satisfy this query from the source in a background thread.
This is preferable to using ParseQuery.find(), unless your code is already running in a background thread.
Returns:
A Task that will be resolved when the find has completed.
Parse's creators prefers that the users of their API use a background thread to perform operations.
It really depends.
Is the user triggering the update? If so then do it on the main thread because you don't want them waiting
If not, then is the data access a result of fetching data from the web (and hence you should already be on a background thread) so could probably just remain on the background thread
Also what happens in "// do whatever I need to do"? Is it an update to the UI or more background processing?

How can I start two asynchronous threads which join to common execution once both are complete?

I'm looking for a design pattern or approach for the following scenario. I wish to kick off two separate background threads for data retrieval from different sources. I then want one method (on the UI thread) to be called once both background threads have completed their work. As the data from the two sources must be combined to be useful, I must wait until both have finished retrieving before manipulating the data. How can I achieve this on the Android platform?
Edit: My first version has been bothering me, and I didn't like the necessary added boolean with it, so here's another version. Call it with this from onPostExecute of each added task.
ArrayList<AsyncTask> tasks;
public void doStuffWhenDone(AsyncTask finishedTask)
{
tasks.remove(finishedTask);
if(tasks.size() > 0)
return;
... do stuff
}
I'll keep the older one up also, since they both work, but I think the above is much cleaner. Now to go tidy up one of my earlier projects.
ArrayList<AsyncTask> tasks;
boolean hasBeenDone = false;
public void doStuffWhenDone()
{
for(int i=0;i<tasks.size();i++)
if(hasBeenDone || (tasks.get(i).getStatus() != AsyncTask.Status.FINISHED))
return;
hasBeenDone = true;
... do stuff
}
It's easily extendable to however many tasks you have, and there's no need for a thread to handle the threads. Just call the method at the end of each task. If it's not the last one done, nothing happens.
Edit: Good point, but I don't think it needs to be atomic. Since both AsyncTasks' onPostExecute methods run on the UI thread, they'll be called one after the other.
Use a CountDownLatch, like this:
CountDownLatch barrier = new CountDownLatch(2); // init with count=2
startWorkerThread1(barrier);
startWorkerThread2(barrier);
barrier.await(); // it will wait here until the count is zero
doStuffWithTheResult();
when a worker thread finishes, call barrier.countDown() from it.
You can use AsyncTask and an int to know if both jobs are finished...

Updating Cursor in background thread

I have a similar problem to the one described in this discussion: I need to refresh a ListView when the underlying database changes, but the query is expensive so I'm doing it in an AsyncTask.
Here's what I do when the updated Cursor is ready. (This is also how the list is initially populated on startup.)
#Override
protected void onPostExecute(Cursor result) {
if (activity != null) {
if (currentCursor != null) {
// existing cursor is closed by adapter.changeCursor() so
// we don't need to explicitly close it here
stopManagingCursor(currentCursor);
}
currentCursor = result;
startManagingCursor(currentCursor);
if (adapter == null) {
adapter = getAdapter(result);
setListAdapter(adapter);
} else {
adapter.changeCursor(result);
}
activity.onGotList(result, dbAdapter);
}
}
Here's the error I get. It doesn't happen every time, which is even more frustrating.
Releasing statement in a finalizer. Please ensure that you explicitly call close() on your cursor: SELECT DISTINCT t._id AS _id, t.amount, t.date, t.memo, t.synced, t.flag, (children.pa
android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here
at android.database.sqlite.SQLiteCompiledSql.<init>(SQLiteCompiledSql.java:62)
at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:100)
at android.database.sqlite.SQLiteQuery.<init>(SQLiteQuery.java:46)
at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:53)
at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1412)
at android.database.sqlite.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1382)
So, I am obviously not closing the Cursor correctly. If I call currentCursor.close() instead of relying on the outgoing Cursor being closed by adapter.changeCursor(), then I get warnings about closing the Cursor twice or closing a null Cursor.
What is the correct way to do this?
In the discussion I linked to, Dianne Hackborn suggests using a Loader instead. That is not an option for me since my code has to run on Android 2.1.
Try to .close() the Cursor when the Activity pause or terminates.
In the onPause() or onDestroy() section of the activity.
Basically, it's possible, but very bad practice to access the same database from two different helpers, so if you have an activity performing database queries, you shouldn't also have a thread accessing it, otherwise android will throw up a quiet error in logcat, and then forget about the query...
The best solution I have found is to implement a thread pool of runnables, each one is a database query and they all use the same database helper. Consequently, only one thread is accessing the database at any one time and the database is just opened and closed when the thread pool starts/stops.
An implementation of the thread pool pattern can be found here: http://mindtherobot.com/blog/159/android-guts-intro-to-loopers-and-handlers/
If you are not changing anything other then redrawing from the list is it necessary to change the cursor at all. Could you get away with just requiring the current adapter.
something along the lines of
adapter.getCursor().requery();
although if you are in a thread other then the main ui thread you may want to call it with
//Did not realize this was deprecated Thanks to Graham Borland for the heads up
runOnUiThread(new Runnable(){
public void run(){
adapter.getCursor().requery();
}
});
Depending on your setup.
New solution still need testing and make sure it is not going to cause issues apparently startManaginCursor and stopManaginCursor are deprecated too so this solution is not worth good either.
stopManagingCursor(adapter.getCursor());
if (!adapter.getCursor().isClosed())
adapter.getCursor().close();
//cursor creation stuff here if needed
startManagingCursor(newCursor);
adapter.changeCursor(newCursor);

Categories

Resources