Android ListFragment data not updating via SimpleCursorAdapter / LoaderManager / notifyChange() - java

I have a ListFragment that shows the names of all the shopping lists stored in my database table.
The problem is that when I add a new shopping list row to the table, the ListFragment on the UI is not automatically updated. (The change can only be seen if I close and restart the app.)
Firstly, this is the code that is executed in my DbContentProvider class when I add a shopping list:
`
// Insert the values into the table
id = db.insert(SHOPPING_LISTS_META_TABLE, null, values);
if (id > -1) {
// Construct and return the URI of the newly inserted row.
Uri insertedId = ContentUris.withAppendedId(CONTENT_URI_SHOPPING_LISTS_META, id);
// Notify any observers of the change in the data set.
Log.d(LOG_TAG, "................. notifyChange(\"" + insertedId + "\", null)");
getContext().getContentResolver().notifyChange(insertedId, null);
Log.d(LOG_TAG, "................. notifyChange() done");
return insertedId;
}
else {
return null;
}
`
...and here is the LogCat output for it...
10-28 12:29:41.133: D/SQLiteOpenHelper(19401): ................. notifyChange("content://org.example.myapp.DbContentProvider/shopping_lists_meta/12", null)
10-28 12:29:41.143: D/SQLiteOpenHelper(19401): ................. notifyChange() done
10-28 12:29:41.153: D/HomeActivity(19401): Shopping list, "My Test Shopping List" created: content://org.example.myapp.DbContentProvider/shopping_lists_meta/12
10-28 12:29:41.183: D/AbsListView(19401): unregisterIRListener() is called
10-28 12:29:41.193: E/ViewRootImpl(19401): sendUserActionEvent() mView == null
10-28 12:29:41.503: D/AbsListView(19401): unregisterIRListener() is called
In LogCat, there is no output at all from my ListFragment class when I add the new shopping list row. Here is my ListFragment class...
`
public class ShoppingListNamesListFragment extends ListFragment implements LoaderManager.LoaderCallbacks {
private final static String LOG_TAG = ShoppingListNamesListFragment.class.getSimpleName();
// This is the Adapter being used to display the list's data
public static SimpleCursorAdapter mAdapter;
// These are the Contacts rows that we will retrieve
static final String[] PROJECTION = {DbContentProvider.KEY_ID,
DbContentProvider.KEY_SHOPPING_LIST_NAME,
DbContentProvider.KEY_IS_SHOPPING_LIST_SELECTED};
// This is the select criteria
static final String SELECTION = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(LOG_TAG, "................. onCreate()");
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(LOG_TAG, "................. onActivityCreated()");
// For the cursor adapter, specify which columns go into which views
String[] fromColumns = {DbContentProvider.KEY_SHOPPING_LIST_NAME};
int[] toViews = {android.R.id.text1}; // The TextView in simple_list_item_1
// Create an empty adapter we will use to display the loaded data.
// We pass null for the cursor, then update it in onLoadFinished()
mAdapter = new SimpleCursorAdapter(this.getActivity(),
android.R.layout.simple_list_item_1, null,
fromColumns, toViews, 0);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(LOG_TAG, "................. onCreateView()");
return super.onCreateView(inflater, container, savedInstanceState);
}
// Called when a new Loader needs to be created
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Log.d(LOG_TAG, "................. onCreateLoader()");
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), DbContentProvider.CONTENT_URI_SHOPPING_LISTS_META,
PROJECTION, SELECTION, null, null);
}
// Called when a previously created loader has finished loading
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Log.d(LOG_TAG, "................. onLoaderFinished()");
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
// Called when a previously created loader is reset, making the data unavailable
#Override
public void onLoaderReset(Loader<Cursor> loader) {
Log.d(LOG_TAG, "................. onLoaderReset()");
// 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);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
makeToast("shopping list clicked: " + position);
Log.d(LOG_TAG, "shopping list clicked: " + position);
}
private void makeToast(String msg) {
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}
}
NB - In my DbContentProvider class, I have...
public static final Uri CONTENT_URI_SHOPPING_LISTS_META = Uri.parse("content://org.example.myapp.DbContentProvider/shopping_lists_meta");
public static final String SHOPPING_LISTS_META_TABLE = "shopping_lists_meta";
I have based my code on this Android ListFragment / LoaderManager example. And none of the similar questions to this that I have found offer a solution that fixes my problem.
I am fairly new to Android, so it could be a simple mistake I've made. But, essentially, it seems to me that when notifyChange() is called in my DbContentProvider class, my ListFragment is not being notified (or there is some other error in this area). Can anyone help?

After spending hours and hours on this, I created a TestListActivity that hooked up to the native Contacts content provider - and that all worked/updated as it should, so I knew the issue was probably in my own content provider that I'd written.
I found the answer here. Turns out I had not called setNotificationUri(ContentResolver cr, Uri uri) on the cursor returned by the query() method of my content provider. (I'm sure this was never mentioned in the Reto Mauer book I was working from...) :/
Anyway, all sorted now! :)

Related

I cant delete some rows by id in SQLite

I always cant delete first two sometimes three records. They are on Listview, when you press element you will see delete button on another layout. On Log im getting correct index for every element.
So here is my code:
Main Activity:
viewOfT.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent i=new Intent(MainActivity.this, popupWindow.class);
i.putExtra("index",id);
startActivity(i);
}
});
}
public void populateListView() {
Cursor data = db.getData();
ArrayList<String> listData = new ArrayList<>();
while(data.moveToNext()){
k.setId(data.getInt(0));
k.setTask(data.getString(1));
listData.add("- " + k.getTask());
}
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
listData);
viewOfT.setAdapter(arrayAdapter);
arrayAdapter.notifyDataSetChanged();
viewOfT.invalidateViews();
Delete button in other activity:
del.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
Bundle bundle=getIntent().getExtras();
long value=bundle.getLong("index");
db.deleteRecord(value);
finish(); }
});
And SQLHelper:
public void deleteRecord(long id) {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, KEY_ID + "=" + id, null);
close();
}
1) Delete populateListView method
2) Add the following as class variables:-
SimpleCursorAdpater sca;
Cursor data;
3) in the onCreate method add :-
data = getData();
sca = new SimpleCursorAdapter(
this, // Context same as for array adapter
android.R.layout.simple_list_item_1, // layout to be used
data, // <<<<<<<< the cursor to be used for the list
new String[]{"columnname_to_be_displayed"}, // <<<<<<<< cursor column to display name
new int[android.R.id.text1], // the view into which the cursor data will be placed
0 // a flag 0 is fine
);
viewOfT.setAdapter(arrayAdapter);
4) Add a new method to override the 'onResume' method (or alter it if already overridden) :-
#Override
protected void onResume() {
super.onResume();
data = getData();
sca.swapCursor(); // can you sca.notifyDatasetChanged()
}
As you are calling another activity to delete the task, onResume will be called when returning from the other activity so the data is again retrieved from the database (deleted row will not exist) and the adpater is told to refresh the data.
You should ideally also override the onDestroy() method to close the cursor (data.close();)
Important Consideration
A cursor column named _id must exist for CursorAdapters (that's how the SimpleCursorAdapter knows what pass to the onItemClickListener).
if KEY_ID does not equate to _id; you either need to change KEY_ID to _id or amend the getData() method to include the _id column (which should be the value of the row's identifier) e.g. assuming a very basic query:
public Cursor getData() {
return db.query(TABLE_NAME,"rowid AS _id, *",null,null,null,null,null);
} // Note! will add an extra column so beware if using column offsets
or perhaps :-
public Cursor getData() {
return db.query(TABLE_NAME,KEY_ID + " AS _id, *",null,null,null,null,null);
} // Note! will add an extra column so beware if using column offsets
A Note on Column offsets
In your code you have :-
k.setId(data.getInt(0));
k.setTask(data.getString(1));
0 and 1 are column offsets (and could change e.g. the two alternative getData() methods). As such it's generally better to take advantage of the Cursor method getColumnIndex(columnname) e.g. the above could be :-
k.setId(data.getInt(data.getColumnIndex(KEY_ID));
k.setTask(data.getString(data.getColumnIndex("TASK_COLUMN")));
Note! not that you will need to create an Array as the SimpleCursorAdpater takes the cursor as the source. (KEY_ID would likely have to be prefixed with the DatabaseHelper Class).

Passing info through an Activity switch

I'm having trouble figuring out how to pass information through an Activity switch.
I currently have an Activity where I can add a client to the database, and I have an Activity to view all clients in a ListView.
What I want is that when the user clicks on a client in the ListView for it to go back to the addclient Activity, with all the fields filled in from the database.
here is the Activity where the user views the clients
public class ViewClientActivity extends Activity
{
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.viewclient_activity);
DBHandler handler = new DBHandler(this, null, null, 1);
SQLiteDatabase db = handler.getWritableDatabase();
final Cursor ClientCursor = db.rawQuery("SELECT * FROM clients", null);
ListView allClients = (ListView) findViewById(R.id.allClients);
final ClientCursorAdapter clientAdapter = new ClientCursorAdapter(this, ClientCursor);
allClients.setAdapter(clientAdapter);
allClients.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Intent AddClientActivity = new Intent(getApplicationContext(), AddClientActivity.class);
startActivity(AddClientActivity);
}
});
}
}
You need to make the client class information implements Parcelable class. This way you will be able to add that information into the Intent you use to open the second Activity. From the second activity, you can get this information from the bundle obtained by calling getIntent()
To add the Parcelable data into de intent use intent.putExtra() and to get it from it use intent.getParcelableExtra()
when he clicks on a client in the listview for it to go back to the addclient activity but with all the fields filled in from the database
When you "go back", you are doing a SELECT *, so everything will be selected and updated correctly, assuming it was updated in the database.
Your task exists within the AddClientActivity to create a new DBHandler and update your data accordingly.
If you need to pass anything, it would be the ID of the row that was clicked, which you can use setTag within the ClientCursorAdapter class's getView method on the inflated view to put. Then, (Long) view.getTag() to receive in the click.
Also, please use getReadableDatabase() for CursorAdapter
This is what i ended up doing to pass the information i needed
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
public void onItemClick(AdapterView<?> parent, View v, int position, long id)
{
IMAGES = imagesPath[0] + "|" + imagesPath[1] + "|" + imagesPath[2] + "|" +
imagesPath[3] + "|" + imagesPath[4] + "|" + imagesPath[5];
Intent intent = new Intent(AddClientActivity.this, ImageViewActivity.class);
intent.putExtra("IMAGES", IMAGES);
intent.putExtra("POSITION", position);
startActivity(intent);
}
});

Android Firebase Database Automatic Refresh UPDATE 2

I have implemented Firebase Database and can successfully add items to it and then display them on a RecyclerView. I also managed to implement Deletion of a child of a database which is successful BUT I need to restart activity to see changes on my phone's screen. For example: when I press Delete on my list item, it disappears from Database instantly but I need to restart the activity to see the change. Here is my code snippets:
private void attachDatabaseReadListener() {
queryRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
locationCurrent = dataSnapshot.getValue(LocationCurrent.class);
locationCurrent.setRefKey(dataSnapshot.getKey());
mLocationAdapter.add(locationCurrent);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
locationCurrent = dataSnapshot.getValue(LocationCurrent.class);
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
I believe I should work on onChildRemoved but have no idea where to start. My main Idea was to repopulate recyclerview using for loop but locationCurrent object I got from datasnapshot is null.
Any ideas where should I start looking for solution? I have also considered to run addValueEventListener method on my query but I run into the problem where I get multiple copies of my single child
UPDATE
Referring to some comments here is my adapter
public class LocationAdapter extends ArrayAdapter<LocationCurrent> {
public LocationAdapter(Context context, int resource, List<LocationCurrent> objects) {
super(context, resource, objects);
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = ((Activity) getContext()).getLayoutInflater().inflate(R.layout.item_location, parent, false);
}
LocationCurrent currentLocation = getItem(position);
TextView nameTextView = (TextView) convertView.findViewById(R.id.nameTextView);
TextView descriptionTextView = (TextView) convertView.findViewById(R.id.descriptionTextView);
nameTextView.setText(currentLocation.getName());
descriptionTextView.setText(currentLocation.getDescription());
return convertView;
}
}
By the way - my Ref Key which is used in locationCurrent class is transient variable and thus not visible in Database.
UPDATE2
After all day of working, I still did not manage to get the item off the adapter as soon it is deleted. Instead - I came up with a temporary solution - I added a recreate() method inside my onChildRemoved and it does it's job. (Not a good practice but still - something)
Basically, this is how I did a removal method.
/**
* This removes the CardView from the RecyclerView, allowing us to create a delete request
*
* http://stackoverflow.com/questions/27293960/swipe-to-dismiss-for-recyclerview/30601554#30601554
*
* #param viewHolder
* #param direction
*/
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// First find the position of the card
// https://www.learn2crack.com/2016/02/custom-swipe-recyclerview.html
int position = viewHolder.getAdapterPosition();
// Connect to the database, identify which card was swiped via the RecyclerViewAdapter
DatabaseReference currRef = adapter.getRef(position);
// Get it out of the DB
currRef.removeValue();
}
The following code above should give you a rough idea of the deletion process. You need to retrieve the index of the element you want to remove. In your context, your location key be binded somehow.
Just FYI, here's the populateViewHolder based a FirebaseRecyclerAdapter.
/**
* Populating the RecyclerView..
*
* #param viewHolder
*
*
* #param task
*
*
* #param position
* With the use of position, we can obtain the key of from the FirebaseDatabase
*
* http://stackoverflow.com/questions/37568703/how-to-get-keys-and-values-using-firebaselistadapter
*/
#Override
protected void populateViewHolder(TaskViewHolder viewHolder, Tasks task, int position) {
// Get the key of the Tasks object
//String currentKey = getRef(position).push().getKey();
//final String currentKey = getRef(position).toString(); // This returns the object URL from Firebase
final String currentKey = getRef(position).getKey();
Log.d(TAG, currentKey.toString());
Log.d(TAG, "Image: " + task.getImageUrl());
// Perform some DateTime formatting from the ISO8601 format
// Basically we need to attach the task to the viewHolder so that
// the cards can instantiate their view properly
viewHolder.setTaskName(task.getTaskName());
viewHolder.setTaskDesc(task.getTaskDescription());
viewHolder.setTaskDate(task.getTaskDeadline());
viewHolder.setTaskImage(task.getImageUrl());
final Intent updateView = new Intent(getActivity(), UpdateTaskActivity.class);
// Implement Serializable on the Tasks object,
// Push the object directly via updateView.putExtra
// That way we can have everything we need in the object.
//updateView.putExtra("TaskName", task.getTaskName());
//updateView.putExtra("TaskDesc", task.getTaskDescription());
updateView.putExtra("TaskObject", task);
updateView.putExtra("Key", currentKey);
viewHolder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
/**
* How to provide a foundation to animate cards
*
* http://stackoverflow.com/questions/27300441/how-do-i-expand-cardviews-to-show-more-detail-like-google-keep-cards
*/
//Toast.makeText(getActivity(), currentKey, Toast.LENGTH_LONG).show(); // Test Line to Showcase the Key.
ActivityOptionsCompat options =
ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
v, // The view which starts the transition
getString(R.string.transition_taskcard) // The transitionName of the view we’re transitioning to
);
ActivityCompat.startActivity(getActivity(), updateView, options.toBundle());
}
});
}
Just showing this example to let you know that both your view and your deletion code must be dynamic.
If you're doing a dynamic view instead, you an refer to the documentation here https://github.com/firebase/emberfire
UPDATE Since you just added your adapter, it is also possible to have an onClickListener within getView to perform any removal as such if you intend to.

Call non-static method of another class

I have two classes pertaining to this issue: AddA and TopicSpinner.
In the AddA class, I am setting an on touch listener for a spinner. When the spinner is selected, I want to call the method loadSpinnerData() of the TopicSpinner class. So I want to call a non-static method of the TopicSpinner class in the AddA class.
I know that starting the TopicSpinner class from the AddA class works with an Intent call so the loadSpinnerData() method does work properly. I tried several approaches in the listener as you see below. However, I cannot get the method to be called without a null pointer or "non-static method cannot be referenced from static method".
Any suggestions of how to call the method loadSpinnerData() of the TopicSpinner class in the listener method of the AddA class?
Here is the current exception:
java.lang.NullPointerException
at android.content.ContextWrapper.getApplicationContext(ContextWrapper.java:109)
at TopicSpinner.loadSpinnerData(TopicSpinner.java:56)
at AddA$2.onTouch(AddAlerts.java:117)
at android.view.View.dispatchTouchEvent(View.java:7241)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2168)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1903)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2174)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1917)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1953)
at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1405)
at android.app.Activity.dispatchTouchEvent(Activity.java:2410)
at android.support.v7.internal.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:59)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1901)
at android.view.View.dispatchPointerEvent(View.java:7426)
at android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:3220)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:3165)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:4292)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:4271)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:4363)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:179)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:125)
at android.os.Looper.loop(Looper.java:124)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
Instead of adding the listener in your AddA class, just add it in your TopicSpinner class. You can do it in your onCreate method. Then you can directly reference your loadSpinnerData method.
public class TopicSpinner extends AddAlerts implements OnItemSelectedListener {
// Spinner element
Spinner spinner;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_alerts);
//Spinner element
spinner = (Spinner) findViewById(R.id.spinner);
//Spinner click listener
spinner.setOnItemSelectedListener(this);
//ADD LISTENER HERE
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
loadSpinnerData();
}
return false;
}
});
//Loading spinner data from database
loadSpinnerData();
}
/**
* Function to load the spinner data from SQLite database
**/
public void loadSpinnerData() {
// database handler
DatabaseHelper db = DatabaseHelper.getInstance(getApplicationContext());
Cursor topicCursor = db.getAllTopics();
Log.v("topicCursor", topicCursor.toString());
db.close();
String str;
ArrayList<String> labels = new ArrayList<String>();
if (topicCursor.moveToFirst()) {
do {
str = topicCursor.getString(topicCursor.getColumnIndex("topic_name"));
Log.v("str", str);
labels.add(str);
Log.v("labels", labels.toString());
} while (topicCursor.moveToNext());
}
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, labels);
//Drop down layout style - list view with radio button
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Log.v("dataAdapter", dataAdapter.toString());
//Attaching data adapter to spinner
spinner.setAdapter(dataAdapter);
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// On selecting a spinner item
String label = parent.getItemAtPosition(position).toString();
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
}
}
By your question, I call tell the confusion between a "Class" and an "Object". By calling a static method, your are calling an action on no particular instance of a TopicSpinner. By calling loadSpinnerData() as it is (not static) you are saying to a precise TopicSpinner, "do the job of loadSpinnerData()". See here to learn more.
I can see in TopicSpinner that you have a field "spinner" that is onwed by "an instance" of TopicSpinner. For example, you could have 2 TopicSpinners having a different Spinner each. The method loadSpinnerData() will actually modify that Spinner. In other words, loadSpinnerData() needs to be called via an instance (object) not via a static call, because it needs to know what Spinner it's actually going to play with.
Now why do you get a Nullpointerexception if you call it correctly? If you call new TopicSpinner() manually, then it means, run the loadSpinnerData() on a new instance of the TopicSpinner that I just created. I have a feeling your topic spinner already exists. You might be able to access it by drilling down in the objects View and MotionEvent. Try to debug and look for the TopicSpinner instance. Then, you can call loadSpinnerData() on that instance.
Hope I didn't confuse you too mch, but you really need to differenciate a class and an instance of a class (object).
Your issue is that "spinner" is not set (is null) in the following line in loadSpinnerData():
spinner.setAdapter(dataAdapter);
When you launch the class with an Intent, it executes onCreate, which properly initializes the "spinner" variable. You could declare "spinner" as an instance variable in AddA, then send it as a parameter to the constructor for TopicSpinner, where you capture and save the reference. Something like this:
public AddA extends Activity {
private Spinner spinner;
private Context context;
//In onCreate or wherever
spinner = (Spinner) findViewById(R.id.spinner);
context = this;
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
TopicSpinner ts = new TopicSpinner(spinner, context);
ts.loadSpinnerData();
}
//etc
Now in TopicSpinner add a constructor:
public class TopicSpinner extends AddA implements OnItemSelectedListener {
// Spinner element
private Spinner spinner;
private Context context;
public TopicSpinner(Spinner spinner, Context context){
this.spinner = spinner;
this.context = context;
}
//etc
In your TopicSpinner class replace getContext() with context.
The rest of your code remains untouched.
You could try passing in the spinner or context in as a parameter?
So copy and paste your load spinner data and sub out with the parameterized spinner and context:
public Spinner loadSpinnerData(Context context, Spinner spinner) {
// database handler
DatabaseHelper db = DatabaseHelper.getInstance(context);
Cursor topicCursor = db.getAllTopics();
Log.v("topicCursor", topicCursor.toString());
db.close();
String str;
ArrayList<String> labels = new ArrayList<String>();
if (topicCursor.moveToFirst()) {
do {
str = topicCursor.getString(topicCursor.getColumnIndex("topic_name"));
Log.v("str", str);
labels.add(str);
Log.v("labels", labels.toString());
} while (topicCursor.moveToNext());
}
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, labels);
//Drop down layout style - list view with radio button
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Log.v("dataAdapter", dataAdapter.toString());
//Attaching data adapter to spinner
spinner.setAdapter(dataAdapter);
return spinner;
}
And then reassign the spinner you passed in to be the new one that comes out of this method. It will hold its previous references as well because we are not assigning it in this method
Old Answer, (left because this 'was' true at the time, but the above code/info has changed)
To start with your first mistake is not storing the 'new' TopicSpinner. That is why you can't call 'member methods(might be wrong name, but 'non-static' methods)' of the class.
final Spinner spinner = (Spinner) findViewById(R.id.spinner);
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// Intent i = new Intent(AddA.this, TopicSpinner.class);
//startActivity(i);
//TopicSpinner ts = new TopicSpinner();
//ts.loadSpinnerData(); //null
// the problem is that you aren't saving the reference to the
// newly created TopicSpinner Object.
// Storing that reference here allows you to call
// non-static methods.
TopicSpinner myTopicSpinner = new TopicSpinner();
// Perhaps this can be above if it follows the builder pattern...
// but if not, then you need to separate these calls.
myTopicSpinner.loadSpinnerData(); // loadSpinnerData you have listed returns 'void' so it doesn't follow builder pattern.
// if myTopicSpinner is null here then you have bigger issues in THAT class.
// Now you can call non-static methods on the OBJECT.
// Since you 'have' an object now.
myTopicSpinner.loadSpinnerData(); //non-static method
}
return false;
}
});
Calling your Activity classes by the names of other classes is seriously confusing people.
AddA is an Activity.
TopicSpinner is also an Activity since you extends AddA.
Calling new TopicSpinner() creates an Activity object, but does not inflate it, so calling setContentView and findViewById are pointless inside of TopicSpinner. It might even return null because the view cannot be found.
As is, you have two options and both get rid of the extends AddA.
Option 1: Just load the data in AddA
Option 2: Create a separate class that loads the data that is not an Activity. You are getting all confused with onCreate and setContentView and findViewById. All you need is the Context and Spinner.
public class AddA extends Activity implements AdapterView.OnItemSelectedListener {
private Spinner spinner;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_a);
spinner = (Spinner) findViewById(R.id.spinner);
spinner.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
/** Option 1 **/
loadSpinnerData();
/** Option 2 **/
TopicLoader loader = new TopicLoader(getApplicationContext());
loader.loadSpinner(spinner);
}
return false;
}
});
}
private void loadSpinnerData() {
Context ctx = getApplicationContext();
// TODO: Implement Option 1 here
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// On selecting a spinner item
String label = parent.getItemAtPosition(position).toString();
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
}
public class TopicLoader {
private Context mCtx;
public TopicLoader(Context ctx) {
this.mCtx = ctx;
}
public void loadSpinner(Spinner spinner) {
// Context ctx = this.mCtx;
// TODO: Implement Option 2 here
}
}
DatabaseHelper db =
DatabaseHelper.getInstance(getApplicationContext()); return null.
If this is the case, then your DatabaseHelper class is where your issues
actually is.
Here is a question with an (almost) complete answer that will show you what to do (you will need to impl. your own rawQuery(), that they don't do in that link)
Looking for Cause of Null Pointer Exception
There are a couple things that you can try. (Hard to tell without seeing source for DatabaseHelper)
1) DatabaseHelper db = new DatabaseHelper(getApplicationContext()); then db.open() right after it. (if you follow the pattern that I show below). Use normal constructor instead of 'getInstance()'. I haven't seen DBHelper classes being singletons or something else that would require 'getInstance()' before. A SQLite DB should be able to handle multiple-concurrent readable DB accesses EASILY (writes have timing issues, but that is 'app logic' not the SQLite DB itself).
2) Start logging within your DatabaseHelper class and see where it fails internally.
Here is (part of) a sample DBHelper class I wrote a long time ago.
You shouldn't be getting a 'null' when you try to instantiate yours. You should make sure that class is operating properly. If you don't have one you should have an internal SQLiteOpenHelper class (very helpful).
// Database open/upgrade helper
private myDbHelper dbHelper; // this is inside my DBAdapter class
// ...
/**
* constructor that accepts the context to be associated with
*
* #param _context
*/
public DataDBAdaptor(Context _context) {
Log.d(LOG_TAG, "MyDBAdapter constructor");
context = _context;
dbHelper = new myDbHelper(context, DATABASE_NAME, null,
DATABASE_VERSION);
}
/**
* open the DB, and write/read access or
* just read access if that is all that is possible.
*
* #return this DataDBAdaptor
* #throws SQLException
*/
public MoocDataDBAdaptor open() throws SQLException {
Log.d(LOG_TAG, "open()");
try {
db = dbHelper.getWritableDatabase();
} catch (SQLException ex) {
db = dbHelper.getReadableDatabase();
}
return this;
}
Here is the Helper class
/**
* DB Helper Class.
*
* #author mawalker
*
*/
private static class myDbHelper extends SQLiteOpenHelper {
public myDbHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
}
#Override
public void onCreate(SQLiteDatabase db) {
Log.d(LOG_TAG, "DATABASE_CREATE: version: " + DATABASE_VERSION);
// ST:createTable:start
db.execSQL(DATABASE_CREATE_STORY);
db.execSQL(DATABASE_CREATE_TAGS);
// ST:createTable:finish
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Log version upgrade.
Log.w(LOG_TAG + "DBHelper", "Upgrading from version " + oldVersion
+ " to " + newVersion + ", which will destroy all old data");
// **** Upgrade DB ****
// TODO: migrate data?? from old DB to new DB
// drop old DB
// ST:dropTableIfExists:start
db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE_STORY);
db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE_TAGS);
// ST:dropTableIfExists:finish
// Create a new one.
onCreate(db);
}
}

notifyDataSetChanged() not working in ListFragment

I have a ListView in a ListFragment that is populated via a database query. It loads all the data just fine when it first populates the list in onCreate(). But when I requery the database, assign the return value to assignmentsCursor, and call notifyDataSetChanged(), it doesn't update adapter.mCursor. If I go into the Debugging mode in Eclipse I can see that assignmentsCursor.mCount has changed, but when I look at adapter.mCursor.mCount, it's the same as before. (Yes, I'm checking after notifyDataSetChanged() has been called.)
The relevant parts of my code:
SimpleCursorAdapter adapter;
Cursor assignmentsCursor;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
course = savedInstanceState.getShort(Values.ASSIGNMENT_KEY_COURSE);
}
setRetainInstance(true);
// Create and array to specify the fields we want
String[] from = new String[] { Values.KEY_TITLE, Values.ASSIGNMENT_KEY_COURSE };
// and an array of the fields we want to bind in the view
int[] to = new int[] { R.id.assignment_list_title, R.id.assignment_list_course };
// Need to give assignmentsCursor a value -- null will make it not work
updateAdapter();
adapter = new SimpleCursorAdapter(context, R.layout.assignment_list_item, assignmentsCursor, from, to);
setListAdapter(adapter);
}
public void onResume() {
super.onResume();
updateAdapter();
refresh();
}
/**
* Updates the content in the adapter.
*/
public void updateAdapter() {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean showingCompleted = sharedPrefs.getBoolean(Values.ASSIGNMENT_KEY_SHOWING_COMPLETED, false);
if (course < 0) // Showing assignments from all courses
if (showingCompleted)
assignmentsCursor = DbUtils.fetchAllAssignments(context, Values.ASSIGNMENT_LIST_FETCH, null, true);
else
assignmentsCursor = DbUtils.fetchIncompleteAssignments(context, Values.ASSIGNMENT_LIST_FETCH, null, true);
else // Showing assignments from specified course
if (showingCompleted)
assignmentsCursor = DbUtils.fetchAllAssignments(context, Values.ASSIGNMENT_LIST_FETCH, course, true);
else
assignmentsCursor = DbUtils.fetchIncompleteAssignments(context, Values.ASSIGNMENT_LIST_FETCH, course, true);
}
private void refresh() {
updateAdapter();
adapter.notifyDataSetChanged();
}
Help me!! ;)
P.S. If you need any more details/code, just let me know. I am positive that the database is querying correctly though.
Thanks to Deucalion, I have determined that I incorrectly assumed that the adapter was referencing the assignmentsCursor variable as opposed to creating a copy of the Cursor for itself. What I needed to do was just call adapter.changeCursor(assignmentsCursor) so that it updated its local copy. Thanks again Deucalion!

Categories

Resources