Passing variables to a ViewBinder - android

I have a ViewBinder that is acting as the middleman between my ListView and a cursor adapter to a database. Depending on the context this data is being displayed in, I need the ViewBinder to do different things.
As an example, take a task management application, that is displaying tasks for all of the task groups that exist. Maybe in this case the app wants to display the name of the task group in the list view, when it wouldn't want to if it was showing tasks for one of the specific task groups. The list view item could have a hidden field, and the ViewBinder can be used to map the task group's name to the field AND set it to visible when necessary (as instructed the main application).
My question is how would one tell the ViewBinder the context in which it is displaying, so it can determine how to behave?
I realize this can likely be done by implementing many different ViewBinders, but this would require much code to be duplicated, and would prefer doing it with a single ViewBinder that I just pass certain parameters to.

This can be done by implementing the constructor for the ViewBinder you are implementing.
Per the example, one can do something like this:
private Boolean displayGroupName = true;
public ToDoViewBinder(Boolean displayGroupName) {
this.displayGroupName = displayGroupName;
}
#Override
public boolean setViewValue(View view, Cursor c, int columnIndex) {
if(displayGroupName)
{
//Do necessary stuff
return true;
}
else
return false;
}

Related

How do I retain selected item highlighting on gridview when numColumns changes?

I have an ActionBarActivity with a GridView.
The GridView has 2 columns in portrait and 3 columns in landscape.
When I select items in portrait (starting my ActionMode) and then rotate the device, the selected item highlighting shifts one item to the left. For example, if I select the second item and rotate, the first item will be highlighted. If I select the first item and rotate, no items are highlighted.
The actual selection in the code is correct, just the highlighting is wrong.
I notice it does not do this if I keep the numColumns the same for portrait and landscape.
I believe this issue started occurring after I changed my activity to an ActionBarActivity so it could be a bug..
Anyone know why or how to fix it?
I had a similar scenario and ended up solving the issue be creating a custom grid item with a boolean field to keep track of whether the item is selected or not and then highlighting the item appropriately through the custom adapter. Below is a rough outline of what I did:
(1) I created a custom grid item with a boolean field, which we will call selectedStatus for simplicity's sake. I also added the corresponding methods to my grid item class to get the selected status:
public boolean getSelectedStatus ()
{
return selectedStatus;
}
public void setSelectedStatus (boolean paramSelectedStatus)
{
this.selectedStatus = paramSelectedStatus;
}
(2) I then created a custom Adapter that extends BaseAdapter to handle the custom grid object I created. In this Adapter I check the if the selected status of the grid object is true or false and highlight the item accordingly, shown below:
#Override
public View getView (final int position, View convertView, ViewGroup parent)
{
// rest of getView() code...
if (!yourGridObject.getSelectedStatus())
{
convertView.setBackgroundColor(Color.TRANSPARENT);
}
else
{
convertView.setBackgroundColor(Color.LTGRAY);
}
// rest of getView() code...
return convertView;
}
(3) Lastly, you add the onItemClickListener to set the selected status and the background color of the grid items when they are selected (clicked):
yourGridView.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
YourGridObject yourGridObject = (YourGridObject) parent.getItemAtPosition(position);
if (!yourGridObject.getSelected())
{
view.setBackgroundColor(Color.LTGRAY);
yourGridObject.setSelected(true);
}
else
{
view.setBackgroundColor(Color.TRANSPARENT);
yourGridObject.setSelected(false);
}
}
});
Implementing selection this way ensures that the highlighting (selection) of the grid items will not change when the number of columns and rows swap since the selection status is contained within the grid objects themselves.
You don't need to manually handle selection of items as suggested by Willis. Android fully supports what you are asking. I will assume you are using an ArrayAdapter however this answer would apply to all adapters. Note some adapters (like CursorAdapter) won't suffer from your posted problem and don't require the following solution because it's already doing it internally.
The problem is solved in two parts. One, the adapter must enable stable Ids. Two, your adapter must actually return stable ids. You will need to extend the ArrayAdapter or which ever adapter you are using. Then ensure you have defined the following methods as shown below.
private class MyAdapter extends ArrayAdapter<YourObjects> {
#Override
public boolean hasStableIds() {
return true;
}
#Override
public long getItemId(int position) {
//Return a unique and stable id for the given position
//While unique, Returning the position number does not count as stable.
//For example:
return getItem(position).methodThatReturnsUniqueValue();
}
}
Most adapters do not enable hasStableIds. It's primarily only used when enabling a choiceMode. Which I assume you are doing here. By returning true, you are essentially telling Android to keep track of activated (highlighted) items based on their ID value instead of their position number.
Even with stable Ids enabled, you have to actually return an ID that is unique and stable across positional changes. Since most adapters do NOT enable stable IDs, they usually only return the position number as the stable id. Technically, if an item's position never changes over time then the position number "could" be used as the stable id. However, the safest way to return a stable/unique ID is to have one assigned to the class object being stored in the adapter and pull from that.

Including Row ID from MySQL Table in Android ListView (for each list item)

When I grab info from a database (I am using MySQL), I'd like to also grab the id of the row and somehow assign it to each row of the 'listView`.
For example, let's say there is a table called fruit. fruit_id of 16 is orange. When the listView displays the list of fruit, and user clicks on a row that shows orange, i'd like to be able to access the fruit_id(16) of that row. But I'm not sure where to "hide" it.
Doing some initial research it seems there are multiple ways one can do this. The simplest might be something with using a tag, is this the best way? if so, how can you assign an id to it?
Create a class named Fruit.
class Fruit {
private int fruit_id;
private String fruit_name;
// Constructors
// Getters and Setters
}
Use an ArrayAdapter<Fruit> as the ListAdapter for your ListView. Then at ListView's onItemClickListener, get the Fruit object and get its id.
If you're using an ArrayAdapter to back your ListView, then #jaibatrik's suggestion is definitely a good one. However, if you're using a CursorAdapter, it's probably easier to exploit the return value of getItemId().
By default, a CursorAdapter will look for a column with the name "_id" in the Cursor you supply it and return that as id value whenever you click an item:
onItemClick(AdapterView<?> parent, View view, int position, long id)
That last value will contain the id of your cursor item. You can also override this behaviour and have it return any other unique value you may have access to in the Cursor.
The same is also true for an ArrayAdapter: by default it will return the position of the item in the array as unique id. However, you could easily make it return fruit_id for every item by overriding that method. Then it'll be passed in the onItemClick(...) directly, which saves you retrieving it (again) in there.
My questions is, if I grab, for example, item_id (not just item),
where do I put item_id in the listView rows (on Android side)?
The beauty of having objects that represent the data you're visualising in the list, is that you already have all the ingredients to make it work. Let's take the Fruit example given by #jaibatrik and add one getter for the sake of this example:
class Fruit {
private int fruit_id;
private String fruit_name;
// Constructors
// Getters and Setters
public int getId() { return fruit_id; }
}
In the comments you're describing you retrieve the fruit data from the database and populate it in a list:
List<Fruit> fruits = ...
That list should be the dataset backing your ArrayAdapter. To be more specific, since it's a typed class, you should have an ArrayAdapter<Fruit> instance that you set as adapter to the ListView.
Now, assuming you have an OnItemClickListener set against the ListView, it will fire whenever the user taps on an item. Using the parameters passed into the callback, you can retrieve the item that is associated with the position that was selected:
#Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fruit fruit = (Fruit) parent.getItemAtPosition(position);
int fruit_id = fruit.getId();
...
}
With the object retrieved, you can do anything you like with the data it holds. No need to explicitly set the id against the row views, since it should already be part of the dataset that backs the ListView.

Obtaining the current Android view and forcing it to be redrawn

How can I get the current Android view when it displays data that has been updated, and force it to be redrawn? I worked through Android's Notepad tutorial and completed lesson three without any problems — the solution is provided, after all — but I'm stuck on my first non-trivial modification.
I added a new button to the menu, next to the Add note button. When pressed, that button adds a letter to the title of each note in the system. However, the new titles don't show up in the list of notes no matter how long I wait. I know the updater works because the changes do appear if I dismiss the app and bring it back up.
So far, I've discovered that I have to use some kind of invalidation method to make the program redraw itself with the new values. I know that invalidate() is used from the UI thread and postInvalidate() is used from non-UI threads 1, 2, but I don't even know which thread I'm in. Also, both of those methods have to be called from the View object that needs drawing, and I don't know how to obtain that object. Everything I try returns null.
My main class:
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch(item.getItemId()) {
case INSERT_ID:
createNote();
return true;
case NEW_BUTTON:
expandTitles();
return true;
default:
// Intentionally empty
}
return super.onMenuItemSelected(featureId, item);
}
private void expandTitles() {
View noteListView = null;
// noteListView = findViewById(R.layout.notes_list); // null
// noteListView =
// getWindow().getDecorView().findViewById(android.R.id.content);
// From SO question 4486034
noteListView = findViewById(R.id.body); // Fails
mDbHelper.expandNoteTitles(noteListView);
}
My DAO class:
public void expandNoteTitles(View noteListView) {
Cursor notes = fetchAllNotes();
for(int i = 1; i <= notes.getCount(); i++) {
expandNoteTitle(i);
}
// NPE here when attempt to redraw is not commented out
noteListView.invalidate(); // Analogous to AWT's repaint(). Not working.
// noteListView.postInvalidate(); // Like repaint(). Not working.
}
public void expandNoteTitle(int i) {
Cursor note = fetchNote(i);
long rowId =
note.getLong(note.getColumnIndexOrThrow(NotesDbAdapter.KEY_ROWID));
String title =
note.getString(note.getColumnIndexOrThrow(NotesDbAdapter.KEY_TITLE)) + "W";
String body =
note.getString(note.getColumnIndexOrThrow(NotesDbAdapter.KEY_BODY));
updateNote(rowId, title, body);
}
What do I have to do to get the updated note titles to show up as soon as I press the button?
Obviously, I'm a complete newbie to Android. I point this out to encourage you to use small words and explain even obvious things. I know this is the millionth "Android not redrawing" question, but I've read dozens of existing posts and they either don't apply or don't make sense to me.
1: What does postInvalidate() do?
2: What is the difference between Android's invalidate() and postInvalidate() methods?
According to the tutorial, the list of existing notes are presented in a ListView. That is an adapter based View, so the items it shows are sourced from an adapter extending theBaseAdapter class. In these cases, you should notify the adapter that the contents have changed by calling its notifyDatasetChanged method. This'll signal the ListView to update and redraw its rows.
Edit:
Sorry, I now realize that this example uses CursorAdapters. These source the items to show from a Cursor object that was obtained from a database query. Now, what the notifyDatasetChanged() tells the adapter is, that the data that backs the adapter has changed, so Views that show stuff based on this adapter need to redraw their contents. In the case of a CursorAdapter, this data is coming from a cursor. So you also need to requery that cursor, refreshing it from the DB, like this:
private void expandTitles() {
mDbHelper.expandNoteTitles();
CursorAdapter adapter = (CursorAdapter)getListAdapter();
adapter.getCursor().requery();
}
The requery() method automatically calls the notifyDatasetChanged() in this case, so you don't need to worry about that, the list will update itself. See this thread also: https://groups.google.com/forum/?fromgroups#!topic/android-developers/_FrDcy0KC-w%5B1-25%5D.

Android ArrayAdapter not clearing?

I'm having a strange issue with a custom implementation of Android's ArrayAdapter. To give some background, I'm trying update a ListView's contents while preserving the current scroll position.
I have a service which executes a thread to update data that's displayed in the ListView. That data is stored in an ArrayList and that ArrayList is used to generate some custom ArrayAdapters for the ListView. The adapters are also updated when an item in the ListView is pressed (either adding or removing an item). I used to just create new adapters each time there was any type of change and then set this new adapter to the ListView. This worked, but caused the ListView to scroll to the top each time. Given the nature of my application this was undesirable. The current scrolled position in the ListView must be maintained between updates.
Instead of creating new adapters I began clearing the adapter that I needed to update using the adapter's clear() method, then rebuild the adapter's items by using the adapter's add() method. Both of these methods are being called on the adapter. The adapters are all set to notifyDataOnChange in their constructors so I don't have to manually call notiftyDatasetChanged() each time (although given my issue I've tried calling it manually as well to no avail).
Here's what my custom adapter looks like:
public class RealmAdapter extends ArrayAdapter<Realm>
{
Context c;
public RealmAdapter(Context context, int resource, int textViewResourceId)
{
super(context, resource, textViewResourceId);
setNotifyOnChange(true);
c = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
...
}
...
}
Long story short, here's my issue. When I call clear() on the adapter, the adapter is not being cleared.
Here's a snippet from my onPostExecute in my thread that does updating. I'm being sure to put it here so it's updating on the UI thread. I also have this exact code copied in a private method in my UI activity. This code does not work in either place:
appState.favoriteAdapter.clear();
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
for(Realm r : appState.favorites) {
appState.favoriteAdapter.add(r);
}
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
As an example, if the above adapter had 3 items in it, calling a getCount() right after the clear() is returning 3 instead of 0. Likewise, if the appState.favorites ArrayList only has 2 items in it, the getCount() after the loop is still returning 3, not 2. Because the adapter is not responding to any of these calls it makes it impossible to update in any fashion. I can post a Logcat later if that will be helpful, but there are no exceptions or anything useful being displayed.
After busting my head for hours, the issue I appear to be having is that the adapter is not responding to calls to any methods that alter it. I've tried passing an empty ArrayList into the adapter's super() call, this does not help. Am I missing something or using the ArrayAdapter incorrectly? I've searched all over and I've already checked a lot of the common problems such as modifying the underlying array and expecting it to update, not calling (or in my casing setting to the adapter) notifyDatasetChanged(), and using an unsupported operation on the underlying collection.
The declaration of the favoriteAdapter is very simple and is contained in my Application class:
public RealmAdapter favoriteAdapter;
Here is the initialization of the favoriteAdapter from above:
if(appState.favoriteAdapter == null) {
appState.favoriteAdapter = new RealmAdapter(c, R.layout.list_item, R.layout.realm_entry, appState.favorites);
}
else {
appState.favoriteAdapter.clear();
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
for(Realm r : appState.favorites) {
appState.favoriteAdapter.add(r);
}
Log.d(LOG_TAG, "COUNT: " + appState.favoriteAdapter.getCount());
}
The above code is in both my UI thread and the thread that downloads the refreshed data.
Underneath the code above a filter is put in place:
if(appState.favoriteAdapter != null && RealmSelector.realmFilter != null) appState.favoriteAdapter.getFilter().filter(RealmSelector.realmFilter.getText().toString());
Would the filter affect clearing the list? Logic would dictate not...
I had filters being applied to the custom ArrayAdapter. Apparently this interferes with adding and removing items from the adapter itself? I added this code to my method and it is now working:
if(appState.favoriteAdapter != null && realmFilter != null) {
appState.favoriteAdapter.getFilter().filter(realmFilter.getText().toString());
}
I'd love if anyone could explain why this matters. I thought filters were meant to select subsets of items in the adapter. In my testing I was leaving the text box that is used for the filter empty, thus no actual filter text should have been applied. Again, if someone knows what's going on and could explain to me why this fixes the problem I'd love to know.

Related Spinners

This application should have four or more related spinners which should reload when their 'parent' spinner' selection changes - as an example with 2 spinners: houses, and rooms - if you choose a house, the room spinner should reload from the sqlite database.
I have tried two approaches: a MySpinner class that takes a "child" Spinner in its constructor and tells the child to update itself when OnSelectedItem is triggered, like so
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (MySpinner.this.mChild.equals(null) == false) {
MySpinner.this.mChild.updateData((int)id);
}
}
the child's updateData is
public void updateData (int parentValue) {
new backgroundTask().execute("create");
}
which background tasks is an AsyncTask to query the sqlite database:
#Override
protected Void doInBackground(String... params) {
Db = new MyDatabase(mContext);
Db.open();
if(params[0] == "create") {
if (mTable.equals("T_room")){
mCursor = mDb.getRooms(mParentValue);
}
}
return null;
}
My second approach has been to create all my spinners directly in the activity.java file. This second approach has me implement one AsyncTask for all 4 or more spinners and choose what to query from the db, based on who calls with what value.
The first approach crashes on the only 'real' line of code in the asynctask, the second approach drives me mad with autosetting spinners and a jumble of ifs in the asynctask.
I'm not a coder by any means, and wonder if someone well versed in object-oriented coding can enlighten me as to what would be good coding behaviour to solve my specific problem (several spinners that update each other on selection.)
This is interesting, at this moment I'm doing something similar. Just keep a reference to the adapter, and inside onItemSelected access the object with adapter.getItem(pos). Then you can use this object to update the second spinner adapter. Just take care of UI threading. I would like to do this in a cleaner way but I don't know how to do it.

Categories

Resources