Populating the ListView item

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.

Shared instance of IPOIDataService

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:

  1. The external storage directory will not be automatically deleted when our app is uninstalled; as we develop our app, we will not face a risk of losing data if we need to uninstall the app. For POITestApp, it was advantageous to always have the data removed.
  2. While running on a physical device, it is much easier to access the external storage directory to copy files to and from than access an apps data directory, which is secured.
  3. Directories inside the external storage directory can be accessed by other apps. In Chapter 8, Adding Camera App Integration, we will add integration with a camera app, and using the external storage directory means we can have the camera app store our picture for us.

Create the POIData class or an equivalent implementation.

Permissions

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:

  1. Double-click on 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:
    Permissions
  2. In the Required permissions list, check WRITE_EXTERNAL_STORAGE and navigate to File | Save.
  3. Switch to the Source view to view the XML as follows:
    Permissions

Creating POIListViewAdapter

In order to create POIListViewAdapter, start by creating a custom adapter as follows:

  1. Create a new class named POIListViewAdapter.
  2. Open the class file, make the class a public class, and specify that it inherits from BaseAdapter<PointOfInterest>.

Now that the adapter class has been created, we need to provide a constructor and implement four abstract methods.

Implementing a constructor

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;
}

Implementing Count { get; }

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; }
}

Implementing GetItemId()

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;
}

Implementing the index getter method

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]; }
}

Implementing GetView()

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.

Reusing row Views

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);

Populating row Views

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

Gone

Tells the parent ViewGroup to treat View as though it does not exist, so no space will be allocated in the layout.

Invisible

Tells the parent ViewGroup to hide the content for the View.

Visible

Tells the parent ViewGroup to display the content of the View.

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;

Hooking up POIListViewAdapter

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;
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.202.187