Navigation drawer SimpleCursorAdapter refresh - android

I'm working on an app which has a navigation drawer which shows a list of options taken from an SQLite table, for which I'm using a SimpleCursorAdapter as follows:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
mDrawerListView = (ListView) inflater.inflate(R.layout.fragment_navigation_drawer, container, false);
mDrawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
selectItem(position);
}
});
mCursorAdapter = getList();
mDrawerListView.setAdapter(mCursorAdapter);
mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
return mDrawerListView;
}
getList() returns a SimpleCursorAdapter, as follows:
private SimpleCursorAdapter getList()
{
Uri uri = Uri.parse("content://" + MyContentProvider.AUTHORITY + "/searches");
Cursor cursor = getActivity().getContentResolver().query(uri,
new String[]
{
SearchTable.COLUMN_ID,
SearchTable.COLUMN_SEARCH_ID,
SearchTable.COLUMN_FULL,
SearchTable.COLUMN_TYPE,
SearchTable.COLUMN_TEXT
},
null, null, null);
if (cursor == null)
{
Log.i(TAG, "FRC! Cursor is null in NavigationDrawerFragment!");
Toast.makeText(getActivity(), getString(R.string.database_error), Toast.LENGTH_SHORT).show();
}
// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
SearchTable.COLUMN_TEXT,
SearchTable.COLUMN_TYPE
};
// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.search_full, R.id.search_type};
// layout for each of the articles in the sidebar
int layout = R.layout.search_title;
// Creates a new SimpleCursorAdapter to bind to the navigation drawer
mCursorAdapter = new SimpleCursorAdapter(
getActivity(),
layout,
cursor,
mWordListColumns,
mWordListItems,
0);
return mCursorAdapter;
}
Unfortunately, when the user refreshes the list of searches there's no change in what is shown in the navigation drawer unless the app is closed, swiped away from the task switcher, and re-launched. My content provider is notifying the app of changes in the tables and I'm watching for them as below:
class MyObserver extends ContentObserver
{
public MyObserver(Handler handler)
{
super(handler);
}
#Override
public void onChange(boolean selfChange)
{
this.onChange(selfChange, null);
}
#Override
public void onChange(boolean selfChange, Uri uri)
{
mCursorAdapter.notifyDataSetChanged();
mDrawerListView.invalidate();
Log.i(TAG,"Cursor dataset changed!");
}
}
I see the log messages for a changed dataset, so that code is actually being called. The fragment's onCreate method contains this:
Uri searchUri = Uri.parse("content://" + MyContentProvider.AUTHORITY + "/searches");
observer = new MyObserver(new Handler());
getActivity().getContentResolver().registerContentObserver(searchUri, true, observer);
What am I missing here? Or, have I misunderstood how notifyDataSetChanged() is supposed to work? As a workaround I was considering destroying and recreating the fragment, but since onChange() gets called several times for multiple inserts/deletions then this might be rather a wasteful hack.
Thanks for any suggestions.

Eventually I managed to find a way to do this, which I don't think is very good but it seems to function adequately for the moment. Every other attempt to invalidate views or notify of changed datasets had no effect.
#Override
public void onPrepareOptionsMenu(Menu menu) // called when drawer opens
{
if (mDrawerLayout != null && isDrawerOpen())
{
mCursorAdapter = getList(); // create a new cursor with the latest data (see above)
mCursorAdapter.notifyDataSetChanged(); // do I even need this?
ListView listView = (ListView) getActivity().findViewById(R.id.navigation_drawer); // bind the new cursor to the listview
listView.setAdapter(mCursorAdapter);
listView.invalidateViews();
...
}
}
Please let me know if you can think of a better way!

Related

Loading two SQL columns into a listview but only need to display the first column Android

I am using an sqllite database to store two columns which are phonename and phonenumber. I am using an arrayList to iterate through the data and display the phonename in a listview which is working, but I also need to iterate through the phonenumber column under the same listview as well. I only need the phonename to be showing in the listview.
This is for when the user has selected the item in the listview, it shows the selected phonename and phonenumber, which at the moment it is only currently showing the phonename and showing blank for phonenumber for obvious reasons.
DataDBAdapter
public long insert(String phonename, String phonenumber)
{
ContentValues cv = new ContentValues();
cv.put(COl_MYTABLE_PHONENAME,phonename);
cv.put(COL_MYTABLE_PHONENUMBER,phonenumber);
return mDB.insert(TBL_MYTABLE,null,cv);
}
//---------------------------------------------------------------------------
// Iterating through the database
//---------------------------------------------------------------------------
public ArrayList<String> getAllRowsAsList()
{
Cursor csr = mDB.query(TBL_MYTABLE,null,null,null,null,null,null);
ArrayList<String> rv = new ArrayList<>();
while (csr.moveToNext())
{
rv.add(csr.getString(csr.getColumnIndex(COl_MYTABLE_PHONENAME)));
}
return rv;
}
SelectModemFragment
private void manageListView(Context context)
{
thelist = dbHelper.getAllRowsAsList(); // Extract the list, just the phone names
// Only setup the adapter and the ListView if the adapter hasn't been setup
if(arrayAdapter == null)
{
// Instantiate the adapter
arrayAdapter = new ArrayAdapter<>(context,android.R.layout.simple_list_item_1,thelist); //<<<<<<<<<< list included
display_contacts1.setAdapter(arrayAdapter); //<<<<<<<<<< Tie the adpater to the ListView
// Set the ListViews OnItemClick Listener
display_contacts1.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
String namedisplay = arrayAdapter.getItem(position); //<<<<<<<<<< this gets the phone name
namedisplay = arrayAdapter.getItem(position);
Toast.makeText(view.getContext(), namedisplay + " Selected for Communication", Toast.LENGTH_SHORT).show();
Toast.makeText(view.getContext(), phoneNo, Toast.LENGTH_SHORT).show();
}
});
}
Issue
using ArrayAdapter only allows a a single item to be passed, thus unless you resort to complicated/messy/inefficient methods ArrayAdapter is only really suitable for a single value.
Fix
You could use an ArrayList where your_object has members for all the required values. i.e phonenumber and phonename. Noting that unless you use a Custom Adapter that you should override the the toString method to extract the data that you want to be displayed, as that is what a standard ArrayAdapter uses.
Alternative (use a CursorAdapter)
An alternative would be to use a Cursor Adapter (e.g. SimpleCursorAdapter), you can then return the Cursor and use it directly. However, a CursorAdapter REQUIRES a column specifically name _id (BaseColumns._ID can be used).
One of the clear advantages of a Cursor adapter is the the 4th paremmter passed to the onItemClick/onItemLongClick is the id of the row (if used correctly) allowing a single value to then get/update/delete/pass the respective selected row.
As such I'd recommend a Cursor Adapter for a ListView and hence the more comprehensive answer.
You may think I don;t have such a column. However, you can use the normally hidden rowid column and dynamically create a column named _id.
You could have a method, in the database helper (DataDBAdapter) such as :-
public Cursor getAllRowsAsCursor()
{
String[] columns = new String[]{"rowid AS " + BaseColumns._ID,"*"}
return = mDB.query(TBL_MYTABLE,null,null,null,null,null,null)
}
The ManageList method could then be :-
private void manageListView(Context context) {
myCursor = dbhelper.getAllRowsAsCursor();
// Only setup the adapter and the ListView if the adapter hasn't been setup
if(arrayAdapter == null)
{
// Instantiate the adapter
arrayAdapter = new SimpleCursorAdapter(context,android.R.layout.simple_list_item_1,myCursor,new String[]{DataAdapter.COl_MYTABLE_PHONENAME},newint[]{android.R.id.text1},0);
display_contacts1.setAdapter(arrayAdapter); //<<<<<<<<<< Tie the adpater to the ListView
// Set the ListViews OnItemClick Listener
display_contacts1.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
String namedisplay = arrayAdapter.getItem(position); //<<<<<<<<<< this gets the phone name
String phonenumber = myCursor,getString(myCursor.getColumnIndex(DataAdapter.COL_MYTABLE_PHONENUMBER);
Toast.makeText(view.getContext(), namedisplay + " Selected for Communication", Toast.LENGTH_SHORT).show();
Toast.makeText(view.getContext(), phonenumber, Toast.LENGTH_SHORT).show();
}
});
} else {
arrayAdapter.swapCursor(myCursor);
}
Notes
MyCursor would be declared as a class variable e.g. Cursor MyCursor;
Instaed of
ArrayAdapter<String> arrayAdapter; you would have
SimpleCursorAdapter arrayAdapter;
The above is in-principle code and has not been tested, so there may be errors and/or omissions.
Working Example
The following is the code based upon the code from the previous question asked (which this appears to follow on from). It has two ListViews the old and a new one that uses a SimpleCursorAdapter. Clicking an item display phone number and also id. Lon Clicking an Item deletes that item (refreshing both ListViews).
DataDBAdapter.java has two new methods (so add these) :-
//<<<<<<<<<< ADDED
public Cursor getAllRowsAsCursor() {
return mDB.query(TBL_MYTABLE,null,null,null,null,null,null);
}
public int delete(long id) {
String whereclause = COL_MYTABLE_ID + "=?";
String[] whereargs = new String[]{String.valueOf(id)};
return mDB.delete(TBL_MYTABLE,whereclause,whereargs);
}
SelectModemFragment.java is now :-
public class SelectModemFragment extends Fragment {
private SelectModemViewModel mViewModel;
ListView display_contacts1;
ArrayAdapter<String> arrayAdapter;
ArrayList<String> thelist;
DataDBAdapter dbhelper;
//<<<<<<<<<< ADDED
ListView display_contacts2;
SimpleCursorAdapter sca;
Cursor MyCursor;
public static SelectModemFragment newInstance() {
return new SelectModemFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.select_modem_fragment, container, false);
display_contacts1 = view.findViewById(R.id.lv001); //<<<<<<<<<< top listview ArrayAdapter<String>
display_contacts2 = view.findViewById(R.id.lv002);
dbhelper = new DataDBAdapter(view.getContext());
AddSomeData();
manageListView(view.getContext());
manageListView2();
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel = ViewModelProviders.of(this).get(SelectModemViewModel.class);
// TODO: Use the ViewModel
}
//Sets up the ListView if not already setup
private void manageListView(Context context) {
thelist = dbhelper.getAllRowsAsList(); //<<<<<<<<<< extract the list (just the phone names) from the database
// Only setup the adapter and the ListView if the adapter hasn't been setup
if (arrayAdapter == null) {
// Instantiate the adapter
arrayAdapter = new ArrayAdapter<>(context,android.R.layout.simple_list_item_1,thelist); //<<<<<<<<<< list included
display_contacts1.setAdapter(arrayAdapter); //<<<<<<<<<< Tie the adpater to the ListView
// Set the ListViews OnItemClick Listener
display_contacts1.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String name = arrayAdapter.getItem(position); //<<<<<<<<<< this gets the phone name
Toast.makeText(view.getContext(),"You clicked the phone named " + name,Toast.LENGTH_SHORT).show();
}
});
} else {
//<<<<<<<<<< MODIFIED to cope with changes (needs to rebuild the array within the adpater)
arrayAdapter.clear();
for (String s: thelist) {
arrayAdapter.add(s);
}
arrayAdapter.notifyDataSetChanged();
}
}
//<<<<<<<<<< ADDED FOR CursorAdapter
private void manageListView2() {
MyCursor = dbhelper.getAllRowsAsCursor();
if (sca == null) {
sca = new SimpleCursorAdapter(
getContext(),
android.R.layout.simple_list_item_1,
MyCursor,
new String[]{DataDBAdapter.COl_MYTABLE_PHONENAME},
new int[]{android.R.id.text1},
0
);
display_contacts2.setAdapter(sca);
display_contacts2.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(view.getContext(),
"You Clicked the phone name " +
MyCursor.getString(MyCursor.getColumnIndex(DataDBAdapter.COl_MYTABLE_PHONENAME)) +
". The phonenumber is " +
MyCursor.getString(MyCursor.getColumnIndex(DataDBAdapter.COL_MYTABLE_PHONENUMBER)) +
". The ID (as passed) is " + String.valueOf(id) +
". The ID (from Cursor) is " + String.valueOf(MyCursor.getLong(MyCursor.getColumnIndex(DataDBAdapter.COL_MYTABLE_ID)))
,
Toast.LENGTH_SHORT).show();
}
});
//<<<<<<<<<< EXTRA delete row on long click
display_contacts2.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
dbhelper.delete(id);
manageListView2();
manageListView(getContext());
return true;
}
});
} else {
sca.swapCursor(MyCursor);
}
}
// Add some testing data (only if none already exists)
private void AddSomeData() {
if (DatabaseUtils.queryNumEntries(dbhelper.getWritableDatabase(),DataDBAdapter.TBL_MYTABLE) < 1) {
dbhelper.insert("Phone 1", "0000000000");
dbhelper.insert("Phone 2", "1111111111");
}
}
#Override
public void onResume() {
super.onResume();
manageListView2();
manageListView(getContext());
}
#Override
public void onDetach() {
super.onDetach();
MyCursor.close();
}
}

Refresh RecyclerView with latest data dynamically

I have a SQLite database in my app for which I made a ContentProvider class.
I also have a RecyclerView into which I load an ArrayList of objects into its adapter to populate the RecyclerView.
Currently, when the activity starts I get a Cursor via my ContentProvider, loop through the Cursor to create an ArrayList of objects that I then set as part of my RecyclerView.Adapter.
All that works, but what I really want is for my RecyclerView to dynamically update as new data is loaded into the SQLite database via the content provider.
I have seen posts listing this library CursorRecyclerAdapter but I do not want to use it because you do not get the nice RecyclerView animations on insert/delete.
I was trying to somehow use the LoaderManager.LoaderCallbacks<Cursor> call back methods to get a cursor, convert to arraylist, then swap that in my RecyclerView adapter but couldn't figure it out.
Could someone please show me some example code on how to set it up in my Activity so that the RecyclerView will refresh when new data is written into the local database via a local content provider?
Here is what my RecyclerView.Adapter looks like:
public class MyAdapter extends RecyclerView.Adapter<AdapterTodoList.Holder> {
private List<TodoItem> itemList;
private Context mContext;
//data
String message;
Long datetime;
//this class takes a context and a list of the items you want to populate into the recycler view
public AdapterTodoList(Context context, List<TodoItem> itemList) {
this.itemList = itemList;
this.mContext = context;
}
#Override
public Holder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
//our xml showing how one row looks
View row = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_todo_item, viewGroup, false);
Holder holder = new Holder(row);
return holder;
}
#Override
public void onBindViewHolder(Holder holder, final int position) {
holder.recyclerLinearLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(mContext, "Recycle Click" + position, Toast.LENGTH_SHORT).show();
}
});
//get one item
TodoItem data = itemList.get(position);
Log.d("Test", "onBindViewHolder position " + position);
message = data.getMessage();
datetime = data.getDatetime();
//convert long to date
String dateString = new SimpleDateFormat("MM/dd/yyyy").format(new Date(datetime));
//set the holder
holder.messageTextView.setText(message);
}
#Override
public int getItemCount() {
return itemList.size();
}
public class Holder extends RecyclerView.ViewHolder {
protected ImageView checkBoxImageView;
protected TextView messageTextView;
protected LinearLayout recyclerLinearLayout;
public Holder(View view) {
super(view);
//checkBoxImageView = (ImageView) view.findViewById(R.id.checkBoxImageView);
messageTextView = (TextView) view.findViewById(R.id.messageTextView);
//the whole view
recyclerLinearLayout = (LinearLayout) view.findViewById(R.id.recyclerItemLinearLayout);
}
}
}
Here is what my Activity looks like so far:
public class HomeRec extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{
private Toolbar mToolbar;
//recyclerview and adapter
private RecyclerView mRecyclerView;
private MyAdapter adapter;
//the swipe refresh layout that wraps the recyclerview
private SwipeRefreshLayout mSwipeRefreshLayout;
//this will hold all of our results from our query.
List<TodoItem> itemList = new ArrayList<TodoItem>();
private Cursor mCursor;
//resources from layout
EditText toDoEditText;
Button cancelButton;
Button addButton;
//variables
private String message;
private long datetime;
//loader
private SimpleCursorAdapter mTodoAdapter;
private static final int TODO_LOADER = 0;
// These indices are tied to Projection. If Projection changes, these
// must change.
public static final int COL_ID = 0;
public static final int COL_MESSAGE = 1;
public static final int COL_DATETIME = 2;
public static final int COL_CHECKED = 3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home_rec);
mToolbar = (Toolbar) findViewById(R.id.app_bar);
//set the Toolbar as ActionBar
setSupportActionBar(mToolbar);
// Initialize recycler view //
mRecyclerView = (RecyclerView) findViewById(R.id.todoRecyclerView);
mRecyclerView.hasFixedSize();
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
//set a grey line divider for each item in recycler view
mRecyclerView.addItemDecoration(
new DividerItemDecoration(this, null, false, true));
// END Initialize recycler view //
//initiate the swipe to refresh layout
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
// Refresh items
refreshItems();
}
void refreshItems() {
// Load items
// ...
// Load complete
onItemsLoadComplete();
}
void onItemsLoadComplete() {
// Update the adapter and notify data set changed
// ...
// Stop refresh animation
mSwipeRefreshLayout.setRefreshing(false);
}
});
//set colors for swipe to refresh
mSwipeRefreshLayout.setColorSchemeResources(
R.color.refresh_progress_2,
R.color.refresh_progress_3);
//fire my asynctask to get data for the first time
new MessagesAsyncTask().execute();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_home_rec, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
//Not sure what to do here or how to make this work.
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
//Not sure what to do here or how to make this work.
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
//Not sure what to do here or how to make this work.
}
public class MessagesAsyncTask extends AsyncTask<Void, Void, List<TodoItem>> {
//the cursor for the query to content provider
private Cursor mCursor;
#Override
protected void onPreExecute() {
}
#Override
protected List<TodoItem> doInBackground(Void... params) {
// A "projection" defines the columns that will be returned for each row
String[] projection =
{
DataProvider.COL_ID, // Contract class constant for the COL_ID column name
DataProvider.COL_MESSAGE, // Contract class constant for the COL_MESSAGE column name
DataProvider.COL_DATETIME, // Contract class constant for the COL_DATETIME column name
DataProvider.COL_CHECKED // Contract class constant for the COL_CHECKED column name
};
// Defines a string to contain the selection clause
String selectionClause = null;
// An array to contain selection arguments
String[] selectionArgs = null;
// An ORDER BY clause, or null to get results in the default sort order
String sortOrder = DataProvider.COL_DATETIME + " DESC";
// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
DataProvider.CONTENT_URI_TODO, // The content URI of the Todo table
projection, // The columns to return for each row
selectionClause, // Either null, or the word the user entered
selectionArgs, // Either empty, or the string the user entered
sortOrder); // The sort order for the returned rows
// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
// Insert code here to handle the error.
} else if (mCursor.getCount() < 1) {
// If the Cursor is empty, the provider found no matches
} else {
// Insert code here to do something with the results
}
//convert cursor to arraylist of objects
while (mCursor.moveToNext()) {
itemList.add(new TodoItem(mCursor.getInt(mCursor.getColumnIndex(DataProvider.COL_ID)),
mCursor.getString(mCursor.getColumnIndex(DataProvider.COL_MESSAGE)),
mCursor.getLong(mCursor.getColumnIndex(DataProvider.COL_DATETIME)),
mCursor.getInt(mCursor.getColumnIndex(DataProvider.COL_CHECKED))
));
}
mCursor.close();
return itemList;
}
#Override
protected void onPostExecute(List<TodoItem> itemList) {
if (!itemList.isEmpty()) {
adapter = new MyAdapter(HomeRec.this, itemList);
mRecyclerView.setAdapter(adapter);
} else {
Toast.makeText(getApplicationContext(), "No data to display", Toast.LENGTH_LONG).show();
}
}
}
}
I m not sure what you need but I think you should add this method To adapter and call once your data was pulled
public void swapItems(List< TodoItem > todolist){
this.mTodoList = todolist;
notifyDataSetChanged();
}
Hope this would help :D
from your question I assume that you are loading the data from the database and somewhere there is a code that is updating the database. And on every update you want to update your RecyclerView, If this is the case continue reading. I am not going to explain this completely but there are a lot of source that will explain you this.
Use BroadcastReciever : In the place where you are updating your database sendBroadcast(). And in the activity use the BroadcastReceiver
example and in the onReceive() function call load the data in your ArrayList and call the adapter.notifyDataSetChanged()
Instead of making new adapter each time in onPostExecute and set it to recyclerview again you can notify adapter after modifying list elements.
OR
If you want to make adapter using arraylist instead of cursoradapter using loader i have made sample for you with data provided by you. You can use this as a reference:
public class DataBaseActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private List itemList;
private MyAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_data_base);
RecyclerView recycle=(RecyclerView)findViewById(R.id.rv_data);
SwipeRefreshLayout swipeRefreshLayout= (SwipeRefreshLayout) findViewById(R.id.srl_data);
recycle.setLayoutManager(new LinearLayoutManager(this));
itemList=new ArrayList();
mAdapter= new MyAdapter(this, itemList);
recycle.setAdapter(mAdapter);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
getContentResolver().notifyChange(DataProvider.CONTENT_URI_TODO, null); //if you are using content provider
//getSupportLoaderManager().restartLoader(100, null, DataBaseActivity.this); // if you are using support lib
//getLoaderManager().restartLoader(100, null, DataBaseActivity.this); //if you are not using support lib
}
});
// getLoaderManager().initLoader(100, null, this); //if you are not using support lib
getSupportLoaderManager().initLoader(100, null, this);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection =
{
DataProvider.COL_ID, // Contract class constant for the COL_ID column name
DataProvider.COL_MESSAGE, // Contract class constant for the COL_MESSAGE column name
DataProvider.COL_DATETIME, // Contract class constant for the COL_DATETIME column name
DataProvider.COL_CHECKED // Contract class constant for the COL_CHECKED column name
};
// Defines a string to contain the selection clause
String selectionClause = null;
// An array to contain selection arguments
String[] selectionArgs = null;
// An ORDER BY clause, or null to get results in the default sort order
String sortOrder = DataProvider.COL_DATETIME + " DESC";
return new CursorLoader(this,DataProvider.CONTENT_URI_TODO, // The content URI of the Todo table
projection, // The columns to return for each row
selectionClause, // Either null, or the word the user entered
selectionArgs, // Either empty, or the string the user entered
sortOrder);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if(data!=null && data.getCount()>0)
{
itemList.clear();
while (data.moveToNext()) {
itemList.add(new TodoItem(data.getInt(data.getColumnIndex(DataProvider.COL_ID)),
data.getString(data.getColumnIndex(DataProvider.COL_MESSAGE)),
data.getLong(data.getColumnIndex(DataProvider.COL_DATETIME)),
data.getInt(data.getColumnIndex(DataProvider.COL_CHECKED))
));
}
}
else
Toast.makeText(getApplicationContext(), "No data to display", Toast.LENGTH_LONG).show();
if(data!=null)
data.close();
mAdapter.notifyDataSetChanged();
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}
For "listening" to your ContentProvider changes, you'll could try to integrate ContentObserver into your ContentProvider, so it can trigger the necessary events when a transaction is done on your ContentProvider. After which, you'll declare an ContentObserver to your CONTENT_URI, then you can trigger an update to your RecyclerView.
More info on implementing ContentObserver here.
A sample code for updating an item in your RecyclerView would be,
public void update(T data){
synchronized (mLock){
if(data != null && mData.contains(data)){
int index = mData.indexOf(data);
mData.set(index, data);
notifyItemChanged(index);
}
}
}
Wherein T is the type of object if your row returns, mLock is just an instance object to acquire a lock, mData the list of items you've provided to your RecyclerView. You get the gist. :D
Hope it helps.
Refresh cursor every second
final Handler handler = new Handler();
final int delay = 1000; //milliseconds
handler.postDelayed(new Runnable(){
public void run(){
//Call cursor loader to refresh cursor
getSupportLoaderManager().restartLoader(LOADER_ID, null, MainActivity.this);
handler.postDelayed(this, delay);
}
}, delay);

Implemented Android AlphabetIndexer but it's not showing

I have followed a few stackoverflow threads, tutorials and what I can gather from the documentation but just can't get the AplhabetIndexer working in Android. The goal is to have an indexed ListView that users can quickly scroll using the letters on the right as per the standard contacts app on your phone. Eventually I'll add section headers in the list and make it filterable as a user types but for now I just want to get the basic list working.
I can load the list and get all my results from the cursor, but I never get the letters appear on the right of the ListView. I've tried different combinations of setting the adapter, including in the onCreateView with a null cursor and then calling changeCursor(cursor) in the onLoadFinished() callback, as well as the current version below which sets up the adapter completely in the onLoadFinished() callback.
Has anyone got a full working version of their setup and adapter code they could share? Preferably using the method of creating the adapter first, then just calling changeCursor(cursor) in the onLoadFinished() callback.
What I have so far:
StoreListAdapter.java
public class StoreListAdapter extends SimpleCursorAdapter implements SectionIndexer {
private AlphabetIndexer mAlphabetIndexer;
public StoreListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to, int flags) {
super(context, layout, cursor, from, to, flags);
if(cursor != null){
mAlphabetIndexer = new AlphabetIndexer(cursor,
cursor.getColumnIndex(StoreEntry.TABLE_ALIAS + StoreEntry.COLUMN_NAME),
"ABCDEFGHIJKLMNOPQRTSUVWXYZ");
mAlphabetIndexer.setCursor(cursor);
}
}
#Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
if(cursor != null){
mAlphabetIndexer = new AlphabetIndexer(cursor,
cursor.getColumnIndex(StoreEntry.TABLE_ALIAS + StoreEntry.COLUMN_NAME),
"ABCDEFGHIJKLMNOPQRTSUVWXYZ");
mAlphabetIndexer.setCursor(cursor);
}
}
#Override
public Object[] getSections() {
if(mAlphabetIndexer != null){
return mAlphabetIndexer.getSections();
}else{
return null;
}
}
#Override
public int getPositionForSection(int sectionIndex) {
if(mAlphabetIndexer != null){
return mAlphabetIndexer.getPositionForSection(sectionIndex);
}else{
return 0;
}
}
#Override
public int getSectionForPosition(int position) {
if(mAlphabetIndexer != null){
return mAlphabetIndexer.getSectionForPosition(position);
}else{
return 0;
}
}
}
StoreListFragment.java
public class StoreListFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
private ListView mListView;
private StoreListAdapter mAdapter;
public static StoreListFragment newInstance() {
StoreListFragment fragment = new StoreListFragment();
return fragment;
}
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public StoreListFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_store_search, container, false);
mListView = (ListView) view.findViewById(R.id.search_result_list);
return view;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
#Override
public void onDetach() {
super.onDetach();
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(
getActivity(), // Parent activity context
StoreProvider.CONTENT_URI, // Table to query
null, // Projection to return
null, // No selection clause
new String[]{getString(R.string.centre_id)}, // No selection arguments
null // Default sort order
);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mListView.setFastScrollEnabled(true);
mListView.setScrollingCacheEnabled(true);
mAdapter = new StoreListAdapter(getActivity().getApplicationContext(), R.layout.store_list_item, data, new
String[]{StoreEntry.TABLE_ALIAS + StoreEntry.COLUMN_NAME}, new int[]{R.id.item_name}, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
mListView.setAdapter(mAdapter);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.changeCursor(null);
}
}
The exact behavior can be found in class FastScroller which is a helper class for AbsListView. There is a piece of code there that decides if "the list is long"
final boolean longList = childCount > 0 && itemCount / childCount >= MIN_PAGES;
MIN_PAGES is defined with value of 4. There you have it, if your list item count is not at least 4x the child count (visible rows) fast scroller and thus alphabet indexer will not appear.
Actually, when I added more test data it started working. Would appear that if your search results are small (I was using about 20 or so) it doesn't kick in. Once I added some dummy data of about 100 or so then it started working.

Custom SimpleCursorAdapter - refresh, update DB

My issue today is related to a custom SimpleCursorAdapter I've implemented. Here are my activities onCreate() and the custom SimpleCursorAdapter :
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
customSharedPreference = getSharedPreferences("myCustomSharedPrefs", Activity.MODE_PRIVATE);
editor = customSharedPreference.edit();
setContentView(R.layout.activity_1);
op = new OperationsClass(getApplicationContext());
op.open();
Cursor cursor = op.getList();
startManagingCursor(cursor);
String[] columns = new String[] { "AAA", "BBB", "CCC"};
int[] to = new int[] { R.id.entry_aaa,R.id.entry_bbb, R.id.entry_ccc};
MyCursorAdapter mAdapter = new MyCursorAdapter(this, R.layout.custom_entry, cursor, columns, to);
this.setListAdapter(mAdapter);
op.close();
}
OperationsClass manages a database and the getList() function returns a cursor of the entries.
public class MyCursorAdapter extends SimpleCursorAdapter{
private Context context;
private MyCursorAdapter here = this;
private int layout;
public MyCursorAdapter (Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c, from, to);
this.context = context;
this.layout = layout;
}
#Override
public View newView(final Context context, Cursor cursor, ViewGroup parent) {
Cursor c = getCursor();
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(layout, parent, false);
int col1 = c.getColumnIndex("aaa");
String name1 = c.getString(col1 );
int col2 = c.getColumnIndex("bbb");
String name2 = c.getString(col2 );
int col3 = c.getColumnIndex("ccc");
int name3 = c.getInt(col3 );
final TextView text1 = (TextView) v.findViewById(R.id.entry_aaa);
final TextView text2 = (TextView) v.findViewById(R.id.entry_bbb);
final TextView text3 = (TextView) v.findViewById(R.id.entry_ccc);
text1.setText(name);
text2.setText(name2);
if (name3 == 0)
text3.setText("Not checked");
else {
text3.setText("Checked");
text3.setOnClickListener(new View.OnClickListener()
{
public void onClick(View view)
{
text3.setText("Not checked");
// Here I would like to update my DB using
// OperationsClass and the SharedPrefs,
// and refresh the ListView with the new
// text value.
}
});
}
}
return v;
}
#Override
public void bindView(View v, final Context context, Cursor c) {
// Same operations as higher
}
}
Basically what I want to achieve is to refresh the ListView when the users clicks on the third column, which means its value changes (has been clicked or has not been). In the same time I wish to update the DB and the SharedPreferences(I could create a new object of both classes and recover from the application context, but that seems pretty heavy).
I also wish to know if there is a way to trigger one of the implemented methods in one activity when an AlertDialog has been opened (in the same app, I actually want to add an element to my database through an AlertDialog and make the Activity that popped it up retrieve a new cursor and refresh its List).
"Basically what I want to achieve is"
#Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
// if (the id of selected view matches what you want) {
boolean checked;
if (text3.getText().toString() == "checked") {
boolean checked = true;
} else {
boolean checked = false;
}
op.updateRead(id, checked);
refreshCursorAdapter();
setSharedPrefs();
// }
"to refresh the ListView when the users clicks on the 3rd column, which means its value changes (has been clicked or has not been)."
private void refreshCursorAdapter() {
Cursor cursor = op.getList();
mAdapter.changeCursor(cursor);
}
"In the same time I wish to update the DB"
private boolean updateRead(long rowId, boolean checked) {
ContentValues args = new ContentValues();
if (checked) {
args.put("read", "1");
} else {
args.put("read", "0");
}
return db.update(DB_TABLE, args, "_id =" + rowId, null) > 0;
}
"and the SharedPrefereces"
private void setSharedPrefs() {
SharedPreferences settings = getSharedPreferences("MYPREFS", 0);
SharedPreferences.Editor editor = settings.edit();
if (checked) {
editor.putBoolean("read", false);
} else {
editor.putBoolean("read", true);
}
editor.commit();
}
"I also wish to know if there is a way to trigger one of the implemented methods in one activity when an AlertDialog has been opened"
Quite honestly i don't understand what the mystique behind this one is. The process would involve copying and pasting the same code else to some other event.
Basically what I want to achieve is to refresh the ListView when the
users clicks on the 3rd column, which means its value changes (has
been clicked or has not been). In the same time I wish to update the
DB and the SharedPrefereces (I could create a new object of both
classes and recover from the application context, but that seems
pretty heavy).
First of all, you shouldn't be implementing that logic in the newView method because that method will not be called for every row due to the recycling. The newView should be used only to build a new row view and nothing more. Use the bindView method for any row logic.
Regarding the code in the onClick method I don't see where do you have problems. Update the database based on your logic and then query again the database for a Cursor with the new data and then use swapCursor() to update the adapter with the new values. This should work but it's not the recommended way mainly because you're doing every database operation on the main UI thread. Don't use the startManagingCursor method because this method runs the queries on the main UI thread, instead have a look at implementing a Loader in your activity to load data off the main UI thread. With a Loader you'll update the database values and then simply restart the Loader to update the list.
I also wish to know if there is a way to trigger one of the
implemented methods in one activity when an AlertDialog has been
opened (in the same app, I actually want to add an element to my
database through an AlertDialog and make the Activity that poped it up
retrieve a new cursor and refresh its List).
You're not saying anything about how you show that AlertDialog. If you want to update the list after you add the new element then use the listeners for the AlertDialog's buttons and the same code as above.

FragmentPagerAdapter - Create dynamic content for each fragment

I have implement FragmentPagerAdapter in my app but it show only a same list of items for each fragment whenever i swipe. I am using SherlockFragmentActivity and i want to show different non static pages for each fragment whenever i swipe to next or previous fragment in fragment pager.
How can i do so ?
Have i listed a list of contacts without any title of the current fragment so that i could know that this is my contacts fragment in my fragment using this code
public class ContactsFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks<Cursor>{
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
public static Fragment newInstance(Context context){
ContactsFragment contactFragment = new ContactsFragment();
return contactFragment;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
// setEmptyText("No phone numbers");
// Create an empty adapter we will use to display the loaded data.
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_1, null,
new String[] {ContactsContract.Contacts.DISPLAY_NAME},
new int[] { android.R.id.text1}, 0);
setListAdapter(mAdapter);
// Start out with a progress indicator.
setListShown(true);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
#Override public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("FragmentComplexList", "Item clicked: " + id);
}
// These are the Contacts rows that we will retrieve.
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
};
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = ContactsContract.Contacts.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
}
But I want to show tile above the contact list. For this i put two more methods in my fragment class which i have listed below
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_pager_list, container, false);
View tv = v.findViewById(R.id.text);
((TextView)tv).setText("Contacts");
return v;
}
But when i run my app " Your app stopped unexpetedly" this popup mesage is come.
Without these methods my app is working fine but without any title of fragment. Whats wrong with it?
hi you can achieve this using view pager here the exmple
you can use this in your one.xml layout
<ListView android:id="#+id/List" android:background="#00000000"
android:scrollbars="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollingCache="false"
android:fastScrollEnabled="true"
android:cacheColorHint="#color/white"
/>

Categories

Resources