There are a number of ways to populate a ListView
item within the Android platform; they all work with some type of adapter, meaning a subtype of BaseAdapter
or some type of class that implements the IListAdapter
interface. For simple lists, you will commonly see the use of ArrayAdapter<T>
.
We will create a subtype of BaseAdapter<T>
as it meets our specific need, works well in many scenarios, and allows for the use of our custom layout.
Prior to creating the adapter, we need to consider how we will get access to an instance of the data service since it will be the source of our POI data. As you may recall from Chapter 4, Creating a Data Storage Mechanism, we simply created an instance within the test fixture. That worked fine for testing; however, in the POIApp
project, we need a single instance that we can use throughout the app, meaning each activity and view needs to share the same instance of the data service. There are many ways to accomplish this; for our purposes, we can create a class that houses a single static field that is an instance of IPOIDataService
. The following code demonstrates this approach:
public class POIData { public static readonly IPOIDataService Service = new POIJsonService( Path.Combine( Android.OS.Environment.ExternalStorageDirectory.Path, "POIApp")); }
Note that in this example, we used the Android.OS.Environment
class to get the path for the external storage directory. This is a storage location different from the one we used in POITestApp
. We are now using the external storage directory for the following reasons:
POITestApp
, it was advantageous to always have the data removed.Create the POIData
class or an equivalent implementation.
Android apps must be granted permissions to accomplish certain tasks. One of these tasks is writing to the external storage directory. You specify permissions that an app requires in the AndroidManifest.xml
file. This allows the installer to show potential users the set of permissions an app requires at install time.
To set the appropriate permissions, perform the following steps:
AndroidManifest.xml
from Properties in the Solution pad. The file will be open in the manifest editor. There are two tabs, Application and Source, at the bottom of the screen, which can be used to toggle between viewing a form for editing the file and the raw XML, as shown in the following screenshot:WRITE_EXTERNAL_STORAGE
and navigate to File | Save.Source
view to view the XML as follows:In order to create POIListViewAdapter, start by creating a custom adapter as follows:
POIListViewAdapter
.BaseAdapter<PointOfInterest>
.Now that the adapter class has been created, we need to provide a constructor and implement four abstract methods.
We need to implement a constructor that will accept all the information we will need to work with to populate the list. Typically, you will at least need two parameters passed in; an Activity
parameter, because we need to use some of its services to create our custom list view, and some form of list that can be enumerated to populate the ListView
item. In our case, we have a global reference to the data service we created, which caches POIs, so we only need a single parameter, an Activity
parameter. The following code shows the constructor from the code bundle:
private readonly Activity _context; public POIListViewAdapter(Activity context) { _context = context; }
The BaseAdapter<T>
class provides an abstract definition for a read-only Count
property. In our case, we simply need to provide the count of POIs that we have in our cache. The following code example demonstrates the implementation from the code bundle:
public override int Count { get { return POIData.Service.POIs.Count; } }
The BaseAdapter<T>
class provides an abstract definition for a method that returns an int
ID for a row in the data source. We can use the position
parameter to access a POI in the cache and return the corresponding ID. The following code example demonstrates the implementation from the code bundle:
public override long GetItemId(int position) { Return POIData.Service.POIs[position].Id.Value; }
The BaseAdapter<T>
class provides an abstract definition for an index getter method that returns a typed object based on a position
parameter passed in as an index. We can use the position
parameter to access the POI in the cache and return an instance. The following code example demonstrates the implementation from the code bundle:
public override PointOfInterest this[int position] { get { return POIData.Service.POIs[position]; } }
The BaseAdapter<T>
class provides an abstract definition for GetView()
, which returns a view instance that represents a single row in the ListView
item. As in other scenarios, you can choose to construct the view entirely in code or to construct it from a layout file. We will use the layout file we previously created. The following code example demonstrates "inflating" a view from a layout file:
var view = _context.LayoutInflater.Inflate(Resource.Layout.POIListItem, null);
The first parameter of Inflate
is a resource ID and the second is a root ViewGroup
, which in this case can be left null
since the view will be added to the ListView
item when it is returned.
The GetView()
method is called for each row in the source dataset. For datasets with large numbers of rows, hundreds or even thousands, it would require a great deal of resources to create a separate view for each row and it would seem wasteful since only a few rows are visible at any given time. The AdapterView
architecture addresses this need by placing row Views into a queue that can be reused as they scroll out of view of the user. The GetView()
method accepts a parameter named convertView
, which is of type View
. When a view is available for reuse, convertView
will contain a reference to the view; otherwise, it will be null
and a new view should be created. The following code example has depicted the use of convertView
to facilitate the reuse of row Views:
View view = convertView; if (view == null) view = _context.LayoutInflater.Inflate(Resource.Layout.POIListItem, null);
Now that we have an instance of the view, we need to populate the fields. The View
class defines a named FindViewById<T>
method, which returns a typed instance of a widget contained in the view. You pass in the resource ID defined in the layout file to specify the control you wish to access. The following code returns access to the nameTextView
and sets the Text
property:
PointOfInterest poi = POIData.Service.POIs [position]; view.FindViewById<TextView> (Resource.Id.nameTextView).Text = poi.Name;
Populating addrTextView
is slightly more complicated because we only want to use the portions of the address we have, and we want to hide the TextView
if none of the address components are present.
The View.Visibility
property allows for controlling whether a view is visible or not. It is based on the ViewStates
enum, which defines the following values:
Value |
Description |
---|---|
|
Tells the parent |
|
Tells the parent |
|
Tells the parent |
In our case, we want to use the Gone
value if none of the components of the address are present. The following code shows the logic in the GetView
:
if (String.IsNullOrEmpty (poi.Address)) view.FindViewById<TextView> (Resource.Id.addrTextView).Visibility = ViewStates.Gone; else view.FindViewById<TextView> (Resource.Id.addrTextView).Text = poi.Address;
The last task related to the adapter is hooking it up to the ListView
item. We need to switch back to the POIListActivity
class and add the following declarations:
ListView _poiListView; POIListViewAdapter _adapter;
Now, within the OnCreate
method, we need to create an instance of POIListViewAdapter
and connect it to the Listview
item. The following code is from the code bundle:
protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); SetContentView (Resource.Layout.POIList); _poiListView = FindViewById<ListView> (Resource.Id.poiListView); _adapter = new POIListViewAdapter (this); _poiListView.Adapter = _adapter; }
3.141.202.187