UPDATE
Thanks to @EmanuelSeibold I was able to pinpoint the problem to the update of the recyclerview. The AsyncTask works in the background just fine, and only the adapter update of the recyclerview freezes the UI.
UPDATE2
I found it was indeed my layout setup. I forgot to remove the nestedScrollView around the RecyclerView. That seemed to cause a rendering conflict.
I dug my way through answers here and blog posts, but just don't seem to be able to find a solution.
I am fairly new to Android development and trying to get an idea on multi-threading.
The scenario: I have an app that holds a SQLite database with course data and implemented a search function that queries that database. This blocks the UI-thread for roughly ~3 seconds. I therefore implemented an AsyncTask to keep the UI responsive, but my UI is still blocked while the search is ongoing.
Thanks in advance!
Here the code:
Search activity
public class Activity_Search extends Activity_Base {
private RecyclerView rv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
setActionBarTitle(R.string.title_search);
findViewById(R.id.searchCourseTitle).setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_ENTER:
startSearch();
break;
default:
return false;
}
return true;
}
});
rv = (RecyclerView) findViewById(R.id.searchRecycler);
Adapter_Search adapter = new Adapter_Search(this, null);
rv.setAdapter(adapter);
rv.setLayoutManager(new LinearLayoutManager(this));
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.search_FAB);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startSearch();
}
});
}
private void startSearch() {
findViewById(R.id.searchCourseTitle).clearFocus();
if (this.getCurrentFocus() != null) {
InputMethodManager imm =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), 0);
}
{getting input}
String[] columns = {...};
String selection = {formatting input};
Async_Search search = new Async_Search(this);
search.execute(columns, new String[]{selection});
}
public void onSearchCompleted(Cursor results) {
((Adapter_Search) rv.getAdapter()).changeCursor(results);
}
}
AsyncTask
public class Async_Search extends AsyncTask<String[], Void, Cursor> {
private Activity activity;
public Async_Search (Activity activity) {
this.activity = activity;
}
@Override
protected Cursor doInBackground(String[]... params) {
SQLiteDatabase db = SQL_Database.getInstance(activity).getWritableDatabase();
String[] columns = params[0];
String selection = params[1][0];
return db.query(...)
}
@Override
protected void onPostExecute(Cursor results) {
((Activity_Search) activity).onSearchCompleted(results);
}
}
Recycler adapter
public class Adapter_Search extends RecyclerCursorAdapter<Adapter_Search.ViewHolder>{
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView name, ects, studipCode, termYear, lecturers, fields;
public ViewHolder (View view) {
super(view);
{id lookups}
}
}
public Adapter_Search(Context context, Cursor cursor) {
super(context, cursor);
}
@Override
public ViewHolder onCreateViewHolder (ViewGroup parent, int ViewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.search_list_entry, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder (ViewHolder viewHolder, Cursor cursor) {
TextView name, ects, studipCode, termYear, lecturers, fields;
name = viewHolder.name;
ects = viewHolder.ects;
studipCode = viewHolder.studipCode;
termYear = viewHolder.termYear;
lecturers = viewHolder.lecturers;
fields = viewHolder.fields;
String termandyear = cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_TERM)) +
String.format("%.2s",cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_YEAR)));
name.setText(cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_COURSE)));
String credits = cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_ECTS)) + " ECTS";
ects.setText(credits);
{and so on}
}
}
Base adapter class
public abstract class RecyclerCursorAdapter <ViewHolder extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<ViewHolder> {
private Context mContext;
private Cursor mCursor;
private boolean mDataValid;
private int mRowIdColumn;
private DataSetObserver mDataSetObserver;
public RecyclerCursorAdapter(Context context, Cursor cursor) {
mContext = context;
mCursor = cursor;
mDataValid = cursor != null;
mRowIdColumn = mDataValid ? mCursor.getColumnIndex("_id") : -1;
mDataSetObserver = new NotifyingDataSetObserver();
if (mCursor != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
}
public Cursor getCursor() {
return mCursor;
}
@Override
public int getItemCount() {
if (mDataValid && mCursor != null) {
return mCursor.getCount();
}
return 0;
}
@Override
public long getItemId(int position) {
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
return mCursor.getLong(mRowIdColumn);
}
return 0;
}
@Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
public abstract void onBindViewHolder(ViewHolder viewHolder, Cursor cursor);
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
if (!mDataValid) {
throw new IllegalStateException("this should only be called when the cursor is valid");
}
if (!mCursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
onBindViewHolder(viewHolder, mCursor);
}
/**
* Change the underlying cursor to a new cursor. If there is an existing cursor it will be
* closed.
*/
public void changeCursor(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null) {
old.close();
}
}
/**
* Swap in a new Cursor, returning the old Cursor. Unlike
* {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
* closed.
*/
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return null;
}
final Cursor oldCursor = mCursor;
if (oldCursor != null && mDataSetObserver != null) {
oldCursor.unregisterDataSetObserver(mDataSetObserver);
}
mCursor = newCursor;
if (mCursor != null) {
if (mDataSetObserver != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
mRowIdColumn = newCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
notifyDataSetChanged();
} else {
mRowIdColumn = -1;
mDataValid = false;
notifyDataSetChanged();
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
return oldCursor;
}
private class NotifyingDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
super.onChanged();
mDataValid = true;
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
mDataValid = false;
notifyDataSetChanged();
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
}
}