First off I'm really new to android (< 4 days).
I'm trying to wrap my head around how database data is linked to Views and widgets.
I've done a few tutorials and I've noticed that Adapters are used to link AdapterViews which are (as I understand it) Views which contain a bunch of identical subviews (eg lists, gallery, etc). So the Adapter is responsible for creating those subviews and populating the data for each one (correct me if I'm wrong).
Now let's say I have a list view which lists Hotels for example. Each row in the list has the Hotel's name and a basic rating (eg 5 star). Now when you tap on a hotel in the list a new activity shows up showing the details of that particular hotel. All the data is in a database. I understand that you have an adapter manage the data<->view link for the list, but what's the best way to then manage data for the hotel details view (which is not a list but just a couple of text views and an image for example)?
Is it best to just pass the ID in the intent and then have the details activity fetch the data from the DB on its own (in this case do I store the query in the details activity?)? Or do you get all the fields you need and put those in the intent directly? Do you need an adapter for a view which doesn't actually generate lots of similar subviews?
I guess a summary of my question is what do you use instead of an adapter when you're not dealing with adapterviews but just simple straightforward single views.
Thanks for any help you can provide.
Is it best to just pass the ID in the intent and then have the details activity fetch the data from the DB
on its own (in this case do I store the query in the details activity?)?
That is what I would recommend.
Do you need an adapter for a view which doesn't actually generate lots of similar subviews?
No. You only use an Adapter with an AdapterView (ListView, Spinner, etc.)
I guess a summary of my question is what do you use instead of an adapter when you're not dealing with
adapterviews but just simple straightforward single views.
Just the Cursor from the database. Get the fields from the (one) row in the Cursor, put them in the EditText widgets, and when the user makes changes, update the row.
#CommonsWare is bang on. As a code example, I was able to DRY things up by creating a helper method to dynamically set the TextViews a little cleaner.
In my onCreate() I have a number of the following lines:
bindTextView(hotel, "uid"); // `hotel` is the Hotel object with attributes.
And then I define bindTextView() below as follows:
protected void bindTextView( Hotel hotel, String attribute ) {
try {
// Get field for object dynamically.
Field field = hotel.getClass().getField(attribute);
// Invoke field "getter" method to get value.
String value = field.get(hotel).toString();
// Get resource id dynamically.
int resourceId = R.id.class.getField(attribute).getInt(null);
// Get element with resource id.
TextView element = (TextView) this.findViewById( resourceId );
// Finally, set the element's text value.
element.setText( value );
} catch (IllegalAccessException e) { e.printStackTrace();
} catch (NoSuchFieldException e) { e.printStackTrace(); }
}
I actually moved this helper method to a base Activity class since it's shared amongst a number of different activities.
I hope that helps some people keep their activities clean.
JP
Related
This question is about Couchbase lite (no Sync Gateway).
I'm new to Couchbase, I managed to use the demo app, but I don't understand it completely.
It contains this code which (as far as I understand, since I'm not native English speaker) retrieve views to populate a listview with the indexes:
// This code can be found in ListsActivity.java
// in the setupViewAndQuery() method
com.couchbase.lite.View listsView = mDatabase.getView("list/listsByName");
if (listsView.getMap() == null) {
listsView.setMap(new Mapper() {
#Override
public void map(Map<String, Object> document, Emitter emitter) {
String type = (String) document.get("type");
if ("task-list".equals(type)) {
emitter.emit(document.get("name"), null);
}
}
}, "1.0");
}
listsLiveQuery = listsView.createQuery().toLiveQuery();
Could anyone give me a hand with what each part is doing?
In which step is the listview populated
Can I change "list/listsByName" in the code (line 3)? What would happen?
Can I emit more than one element?
The code is a little bit convoluted. Let's answer the easy parts first.
Can I change "list/listsByName" in the code (line 3)?
Yes. That's just the name of the Couchbase View. You choose the View name. Unfortunately the terms used in Couchbase and Android overlap some. A Couchbase View is a kind of static index of your database.
Can I emit more than one element?
Yes. You can emit most anything you want. Take a look at the documentation here
Now, tracing how the Android ListView gets updated:
In ListsActivity.java notice in the onCreate method a ListAdapter instance gets added to the ListView. This ListAdapter is a private inner class that extends LiveQueryAdapter.
LiveQueryAdapter is in the utils subpackage. If you look at its constructor, you'll see it adds a change listener to the query passed in. When triggered, this change listener sets an enumerator equal to the rows passed back by the live query, then calls notifyDataSetChanged to tell the list to refresh itself. That, in turn, causes getView in ListAdapter to get called. That's where the data is pulled from the database and used to populate a list entry.
I have implemented a firebase list adapter to load a list of items (List A). My data structure is setup such that within List A, each item also contains a reference id to some information located somewhere else (isNice) in the database. Likewise:
ListA
- ObjA
- title : "hi"
- id : "ObjAid"
isNice
- ObjAid : "true"
I'm currently using another database operation to look up the id in the "isNice" child, passing the "ObjAid", the list position, and then a resultReceiver to get the result back. My problem is, when the resultReceiver get a resultData (the value "true"), I have no idea how modify the data within firebase list at the specific position.
My past experience is to use load my data into my own ArrayList and create a custom adapter for the listView, in that case, I could easily update the populated view as extra information is loaded. I would like to avoid setting up my own ArrayList to store the data for the time being, favoring the simplicity of the FirebaseListAdapter. All tips are appreciated, thx :)
After some trial and error, I've instead gone with the RecyclerView + FirebaseRecyclerViewAdapter approach. Using recycler is better for the long run too, in my use case the list could get to 3K+. Using RecyclerView.findViewHolderForAdapterPosition() I can reference the specific itemView after I get the data from the result receiver likewise:
// Reference the Item's view at the specific position
myViewHolder itemViewHolder = recyclerView.findViewHolderForAdapterPosition(position);
// Reference and set the views and here......
I have ListView that is populated like so:
ArrayList<MyFullDataClass> myFullDataClassList = Utilities.getDataFromSQLite(getActivity()); // populate list from SQLite
ArrayAdapter<MyFullDataClass> adapter = new ArrayAdapter<MyFullDataClass>(getActivity(), android.R.layout.simple_list_item_1, MyFullDataClassList);
setListAdapter(adapter);
MyFullDataClass contains many things: Name, address, phone, email, web site, etc. So as it is, each row of the list contains all of this information. It's a little busy. I would like to make it so that each row in the list contains only, say, name and email (and then touching the row would popup with all information in MyFullDataClass)
I could do this by creating a class, call it MyPartialDataClass, that contains fields for only name and email, then create an ArrayList<MyPartialDataClass> and copy data from myFullDataClassList to myPartialDataClassList and use this partial class for the adapter. (Then when a row is clicked, use myFullDataClassList.)
Not particularly elegant, but it would work.
Is there a better way?
I don't think there's a need to have two separate classes which ultimately hold the same information.
Having one class to hold necessary information is fine, unless you absolutely need minute efficiency gains of having two classes. What I would do is make your Adapter take a MyFullDataClass object list, but only populate the views with name and email.
From there, you can listen for an onClick event on your Adapter and pass the MyFullDataClass object associated with the clicked view to a fragment which will display the rest of the information associated with the MyFullDataClass object (i.e. the fragment will display address, phone, etc in addition to name and email).
You wouldn't make two separate tables in a database to hold parts of the same information. You would select what rows/properties from each entry that you need. The same concept applies here, IMO.
)
I found a tutorial about using the ListView in android. But I have a question about it. Here the tutorial:
http://www.vogella.com/articles/AndroidListView/article.html#listadvanced_interactive
You have to scroll to section 13.2.
The idea of the tutorial is on one hand you have a ListView (with checkboxes in each item), on the other hand you have an ArrayList (the items of the ArrayList are objects, which contain the information which are displayed in the items of the ListView, e.g. CheckBox checked or not, text etc.). The adapter schould keep both things equal. If you change the ArrayList, the ListView will be changed, too.
But now my question. If the user touch on one item of the List, the adapter will call the method "onCheckedChanged". But what happen there? An object will create there and get a tag from the the CheckBox. Ok. Now the method is done. The garbage collector will destroy the object or doesn't? What is when I need this infomation from there in my Activity. Imagine I have a button "Delete" under my list. So I have to transfer these information from the listener of the adapter to my Activity. How I can ensure that I use the same ArrayList with the right information in every class?
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
Model element = (Model) viewHolder.checkbox
.getTag();
element.setSelected(buttonView.isChecked());
}
I hope you know what I mean. Can you explain it to me please?
Sorry for the language, but english isn't my mother tongue.
Bye
If I understand your question, the response is in the Chapter 14.1 of the tutorial you gaved...
If you want to do some treatment when delete is checked you add a listener in the ListView.
These functions normally have the position clicked and you can do whatever you want knowing the position clicked. For exemple:
list.setOnItemLongClickListener(new OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(MyList.this,
"Item in position " + position + " clicked",
Toast.LENGTH_LONG).show();
// Return true to consume the click event. In this case the
// onListItemClick listener is not called anymore.
return true;
}
});
I'm not totally sure I understand you're question. There's a lot to unpack in there but I will give it a shot. To understand what's going on you need to understand the relationship between the ListView and the data that backs it which is contained in the ListAdapter.
The ListView displays the data contained in the ListAdapter. The ListAdapter gets its data from some other source, in this case its the ArrayList that you're referring to. Why use an ArrayList? In the tutorial its not entirely clear because the example uses a fixed set of data (e.g. a list of operating systems) that are written out as strings. In practice, however, often times you are using a dynamic list, like a list of users, places, whatever.
The ArrayList holds the objects that will eventually be displayed. This is the "models" class in the tutorial. The same applies for any other class of data, like a list of places. If you imagine a place as a class, that class would contain fields that describe the place (e.g. address, a description, some other unique features). As data is downloaded from some location, a new instance of place would be created for each location. Those objects would be collected in an ArrayList. When the downaloding process was finished, you would then begin the process of passing the contents of that ArrayList to the ListAdapter so that the ListView could eventually be updated.
How do you know you will always use the right ArrayList? Because your ArrayList is linked to you ListAdapter. The data the the ListAdapter processes, whether a list of models or users, is supplied by the ArrayList. You cannot use the wrong one. The same goes for the ListAdapter. Because you must set the adapter that the ListView is linked it it will always pull from the correct source. You cannot use the wrong data.
What's happening with the onCheckChanged listener, if you pay close attention, is that the user is really just updating a field contained in the model class. Each modle has an isChecked field. When the check box is clicked, that value for the object is updated. This is how you know what is checked and what is not.
Response to: "Nobody told the adapter "Hey it belongs to the list, which is your source"
Look at the documentation for ArrayAdapter
and for ListView
The constructors for the ArrayAdapter all take some list of object as an argument. This list would be the list of object that will be displayed within the ListView.
Additionally the ListView requires that you call setAdapter which takes some ListAdapter as its argument. This is what actually tells the ListView the source of the data it is going to display.
In that sense, the List, ListAdapter, and ListView are all linked together.
I have a ListView where I want each item to have an ID number attached to it (not the same as the position number). I was hoping this could be done by setting a tag to each View item in the ListView using setTag() when these Views are being created.
Right now I'm creating the ListView like this:
final ListView listview = (ListView) findViewById(R.id.listView1);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1, names);
listview.setAdapter(adapter);
The names variable in the ArrayAdapter parameters above is an ArrayList, and each string value in this list also has a unique ID that I want to link to this string somehow.
Is there any way I can get access to and modify each of the Views with a tag? One idea was to create my own extended class of ArrayAdapter and override the getView() method, but I don't really understand how it works and how I would go about doing this.
Or is there a better way to link IDs with each string like this than adding tags like I'm trying to do?
Create a ViewBinder and set the tags as the ListView is being populated with whatever you need. You can check all properties of the view to determine what tag goes where, so this should be what you're looking for.
myAdapter.setViewBinder(new MyViewBinder());
public class MyViewBinder implements ViewBinder {
#Override
public boolean setViewValue(View view, Object data, String text){
//Since it iterates through all the views of the item, change accordingly
if(view instanceof TextView){
((TextView)view).setTag("whatever you want");
}
}
}
I just used this exact same answer on another question (albeit slightly different) yesterday.
about getView , it works by using a method of recycling views. i will try to explain it in a simple way.
suppose you have tons of items that can be viewed . you don't want to really create tons of views too , since that would take a lot of memory . google thought of it and provide you the means to update only the views that need to be shown at any specific time.
so , if there is an empty space on the listview , it will be filled with a new view . if the user scrolls , the view that becomes hidden is recycled and given back to you on the getView , to be updated with the data of the one that is shown instead .
for example , if you scroll down , the upper view becomes hidden for the end user , but in fact it becomes the exact same view that is on the bottom .
in order to understand how to make the listview have the best performance and see in practice how and why it works as i've talked about , watch this video:
http://www.youtube.com/watch?v=wDBM6wVEO70
as for tags , i think you want to do something else , since the data itself (usually some sort of collection, like an arrayList) already knows where to update , because you get the position via the getView . if you want a specific view to update , you might be able to do so by using a hashmap that keeps upadting , which its key is the position in the collection , and the value is the associated view . on each time you go to getView , you need to remove the entry that belong to the view (if exists) and assign the new position with the view that you got/created .
Thanks for the answers. thisMayhem's answer would probably have been easier in the end, but on my quest to learn more I ended up making my own adapter according to this tutorial. I pass down the names and the IDs into the adapter and set the names as the text of the TextViews and the IDs as the tags.
I would rather go with the solution discussed in this thread. It is always the easiest to have all related data in same place and in this case you just create a class to hold all the information you will need for every item.