Uses a RecyclerView to display a list of contacts fetched from a ContentProvider using a CursorLoader.
1. ShowContactBook Class where show recycler view
public class ShowContactBook extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{
private static final String TAG = MainActivity.class.getSimpleName();
private static final int TASK_LOADER_ID = 0;
private CustomCursorAdapter mAdapter;
RecyclerView mRecyclerView;
Toolbar toolbar;
CollapsingToolbarLayout collapsingToolbarLayout;
TextView textViewTotalContacts;
SearchView searchView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_contact_book);
collapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
toolbar = findViewById(R.id.toolbar);
textViewTotalContacts = findViewById(R.id.total_contacts);
searchView=findViewById(R.id.search);
setSupportActionBar(toolbar);
int color = ContextCompat.getColor(this, R.color.black);
Drawable icon = AppCompatResources.getDrawable(this, R.drawable.arrow_back_24);
if (icon != null) {
icon = DrawableCompat.wrap(icon);
DrawableCompat.setTint(icon, color);
toolbar.setNavigationIcon(icon);
}
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onBackPressed();
}
});
collapsingToolbarLayout.setTitle("Contacts");
mRecyclerView = findViewById(R.id.list);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new CustomCursorAdapter(this);
mRecyclerView.setAdapter(mAdapter);
/*
Add a touch helper to the RecyclerView to
recognize when a user swipes to delete an
item. An ItemTouchHelper enables touch
behavior (like swipe and move) on each
ViewHolder,and uses callbacks to signal when
a user is performing these actions.
*/
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;
}
//* Called when a user swipes left or right on a ViewHolder*/
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
//* Here is where you'll implement swipe to delete*/
int id = (int) viewHolder.itemView.getTag();
String stringId = Integer.toString(id);
Uri uri = ContactBookContract.contactEntry.CONTENT_URI;
uri = uri.buildUpon().appendPath(stringId).build();
/* or
// Build the URI directly without converting the id to a string
Uri uri = ContentUris.withAppendedId(TaskContract.TaskEntry.CONTENT_URI, id);
*/
getContentResolver().delete(uri, null, null);
getSupportLoaderManager().restartLoader(TASK_LOADER_ID, null, ShowContactBook.this);
}
}).attachToRecyclerView(mRecyclerView);
mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
showItemCount();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
showItemCount();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
showItemCount();
}
});
searchView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
searchView.setIconified(false);
Toast.makeText(ShowContactBook.this, "Searching", Toast.LENGTH_SHORT).show();
}
});
/*
Ensure a loader is initialized and active. If the loader doesn't already exist, one is
created, otherwise the last created loader is re-used.
*/
getSupportLoaderManager().initLoader(TASK_LOADER_ID, null, this);
}
private void showItemCount() {
int count = mAdapter.getItemCount();
String message = count + " Contacts";
textViewTotalContacts.setText(message);
}
@Override
protected void onResume() {
super.onResume();
getSupportLoaderManager().restartLoader(TASK_LOADER_ID, null, ShowContactBook.this);
}
@SuppressLint("StaticFieldLeak")
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {
return new AsyncTaskLoader<Cursor>(this) {
Cursor mTaskData = null;
@Override
protected void onStartLoading() {
if (mTaskData != null) {
deliverResult(mTaskData);
} else {
forceLoad();
}
}
@Override
public Cursor loadInBackground() {
try {
return getContentResolver().query(ContactBookContract.contactEntry.CONTENT_URI,
null,
null,
null,
ContactBookContract.contactEntry._ID + " DESC");
} catch (Exception e) {
Log.e(TAG, "Failed to asynchronously load data.");
e.printStackTrace();
return null;
}
}
public void deliverResult(Cursor data) {
mTaskData = data;
super.deliverResult(data);
}
};
}
/**
* Called when a previously created loader has finished its load.
*
* @param loader The Loader that has finished.
* @param data The data generated by the Loader.
*/
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
// Update the data that the adapter uses to create ViewHolders
mAdapter.swapCursor(data);
}
/**
* Called when a previously created loader is being reset, and thus
* making its data unavailable.
* onLoaderReset removes any references this activity had to the loader's data.
*
* @param loader The Loader that is being reset.
*/
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
}
1. CustomCursorAdapter Class : The CustomCursorAdapter class is a custom implementation of the RecyclerView.Adapter used to display data from a Cursor in a RecyclerView. In the context of the provided code, this adapter is used to display a list of contacts fetched from a ContentProvider.
public class CustomCursorAdapter extends RecyclerView.Adapter<CustomCursorAdapter.TaskViewHolder> {
private Cursor cursor;
private Context mContext;
/**
* Constructor for the CustomCursorAdapter that initializes the Context.
*
* @param mContext the current Context
*/
public CustomCursorAdapter(Context mContext) {
this.mContext = mContext;
}
/**
* Called when ViewHolders are created to fill a RecyclerView.
*
* @return A new TaskViewHolder that holds the view for each task
*/
@Override
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext)
.inflate(R.layout.list_view, parent, false);
return new TaskViewHolder(view);
}
/**
* Called by the RecyclerView to display data at a specified position in the Cursor.
*
* @param holder The ViewHolder to bind Cursor data to
* @param position The position of the data in the Cursor
*/
@Override
public void onBindViewHolder(TaskViewHolder holder, int position) {
int idIndex = cursor.getColumnIndex(ContactBookContract.contactEntry._ID);
int nameColumnIndex = cursor.getColumnIndex(ContactBookContract.contactEntry.COLUMN_CONTACT_NAME);
int phColumnIndex = cursor.getColumnIndex(ContactBookContract.contactEntry.COLUMN_CONTACT_PH);
int emailColumnIndex = cursor.getColumnIndex(ContactBookContract.contactEntry.COLUMN_CONTACT_EMAIL);
int imageColumnIndex = cursor.getColumnIndex(ContactBookContract.contactEntry.COLUMN_CONTACT_IMAGE);
cursor.moveToPosition(position);
final int id = cursor.getInt(idIndex);
String name = cursor.getString(nameColumnIndex);
String ph = cursor.getString(phColumnIndex);
String email = cursor.getString(emailColumnIndex);
//* Extract the image byte array from the cursor*/
byte[] imageByteArray = cursor.getBlob(imageColumnIndex);
//* Convert byte array back to Bitmap*/
Bitmap bitmap = BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.length);
holder.itemView.setTag(id);
holder.nameTextView.setText(name);
holder.circleImageView.setImageBitmap(bitmap);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int id = (int) holder.itemView.getTag();
Uri uri = ContentUris.withAppendedId(ContactBookContract.contactEntry.CONTENT_URI, id);
Toast.makeText(mContext, "" + uri.toString() + " : " + name, Toast.LENGTH_SHORT).show();
Intent intent = new Intent(mContext, ContactDetail.class);
intent.setData(uri);
mContext.startActivity(intent);
applyTemporaryHoverEffect(holder.itemView);
}
});
}
private void applyTemporaryHoverEffect(View view) {
final int originalBackgroundColor = view.getSolidColor();
final int hoverColor = ContextCompat.getColor(mContext, R.color.hoverColor);
final int duration = 100;
view.setBackgroundColor(hoverColor);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
view.setBackgroundColor(originalBackgroundColor);
}
}, duration);
}
/**
* Returns the number of items to display.
*/
@Override
public int getItemCount() {
if (cursor == null) {
return 0;
}
return cursor.getCount();
}
/**
* When data changes and a re-query occurs, this function swaps the old Cursor
* with a newly updated Cursor (Cursor c) that is passed in.
*/
public Cursor swapCursor(Cursor c) {
// check if this cursor is the same as the previous cursor (mCursor)
if (cursor == c) {
return null;
}
Cursor temp = cursor;
this.cursor = c;
if (c != null) {
this.notifyDataSetChanged();
}
return temp;
}
//* Inner class for creating ViewHolders*/
class TaskViewHolder extends RecyclerView.ViewHolder {
TextView nameTextView;
CircleImageView circleImageView;
/**
* Constructor for the TaskViewHolders.
*
* @param itemView The view inflated in onCreateViewHolder
*/
public TaskViewHolder(View itemView) {
super(itemView);
nameTextView = (TextView) itemView.findViewById(R.id.name);
circleImageView = (CircleImageView) itemView.findViewById(R.id.list_imageView);
}
}
}