• No results found

return loader;

}

The implementation of onCreateLoader() is similar to the example discussed in Chapter 6, “Content Providers,” where cursor loaders were introduced. The

DevicesContract.DeviceManufacturer contract class is used to specify the projection of the query. Since DeviceListActivity needs to display every device in the database, no selection criteria are passed to the cursor loader constructor. Other than the projec-tion and the URI of the data to be returned by the content provider, the only other non-null parameter passed to the cursor loader constructor is the last parameter, which defines the sort order. Passing the DevicesContract.DeviceManufacturer.MODEL value causes the content provider to issue a query to the database which sorts the result set by the model name. This causes the list of devices to be displayed in alphabetical order by

DeviceListActivity.

Handling Returned Data

Once the cursor loader has been created and returned from onCreateLoader(),

DeviceListActivity needs to wait for the data to be returned from the content provider in a call to onLoadFinished(). Because the loader manager causes the database read operation to happen on a background thread, the main thread will not be blocked, so there is no fear of an application not responding (ANR) error while the activity waits for the data to be returned.

Listing 7.10 shows the onLoadFinished() implementation where DeviceListActivity

starts to interact with the data returned from the content provider.

Listing 7.10 Processing a Cursor in onLoadFinished()

@Override

public void onLoadFinished(Loader<Cursor> loader, Cursor data) { if (data == null || data.getCount() == 0) {

empty.setVisibility(View.VISIBLE);

recyclerView.setVisibility(View.GONE);

} else {

empty.setVisibility(View.GONE);

recyclerView.setVisibility(View.VISIBLE);

((DeviceCursorAdapter)recyclerView.getAdapter()).swapCursor(data);

} }

ptg18221911 When onLoadFinished() is called, it firsts check to validate that it has received a

cursor that has a result set. If the returned cursor is null, or has no rows in the result set, the RecyclerView is hidden and a view is shown to indicate that there is no data to be shown. Figure 7.1 shows the DeviceListActivity in this empty state.

The empty state of a list activity provides a good opportunity to instruct the user on how to add data to the app. DeviceListActivity instructs the user to click the “+”

button in order to start populating the device list.

If the cursor returned from the content provider in onLoadFinished() contains data, the “empty state” view is hidden, and the RecyclerView is shown so the data can be presented to the user. In addition, the RecyclerView’s adapter (which was set in

onCreate()) is retrieved from the RecyclerView and then updated with the cursor from the content provider by making a call to swapCursor(). Once the adapter is updated with the new data, it starts processing the cursor and displays the cursor contents to the user.

Before looking at the implementation of DeviceCursorAdapter, let’s take a quick look at the onLoaderReset() method which also needs to be implemented because it is part of

Figure 7.1 Device list in an empty state

ptg18221911 Accessing a Content Provider from an Activity 151

the LoaderManager.LoaderCallbacks<Cursor> interface. The onLoaderReset() method implementation is shown in Listing 7.11.

Listing 7.11 Loading a New Cursor with onLoaderReset()

@Override

public void onLoaderReset(Loader<Cursor> loader) {

((DeviceCursorAdapter) recyclerView.getAdapter()).swapCursor(null);

}

The implementation of the onLoaderReset() method is pretty simple. It retrieves the

RecyclerView’s adapter and sets its cursor value to null to prevent the RecyclerView

from performing any additional processing on a cursor that could be invalid.

The first interaction with the DeviceCursorAdapter, other than the call to its constructor, is the call to DeviceCursorAdapter.swapCursor() on onLoadFinished(). The DeviceCursorAdapter.swapCursor() implementation is shown in Listing 7.12.

Listing 7.12 Implementing DeviceCursorAdapter.swapCursor()

private class DeviceCursorAdapter

extends RecyclerView.Adapter<DeviceViewHolder> { public void swapCursor(Cursor newDeviceCursor) {

if (deviceCursor != null) { deviceCursor.close();

}

deviceCursor = newDeviceCursor;

notifyDataSetChanged();

} }

The DeviceCursorAdapter.swapCursor() method mimics the behavior found in the CursorAdapter class that can be used with ListView. It closes the previous cursor, if it was not null, updates its internal state to use the new cursor, then makes a call to

DeviceCursorAdapter.notifyDataSetChanged(). The call to DeviceCursorAdapter.

notifyDataSetChanged() causes the RecyclerView to update itself with the new data from the adapter.

While the RecyclerView is updating in response to the DeviceCursorAdapter.

notifyDataSetChanged() method, it makes a call to DeviceCursorAdapter.

getItemCount() to get the number of items in the data set. Because the adapter is

ptg18221911 backed by a cursor, the number of items in the adapter is the same as the number of

rows in the cursor, or 0 if the cursor is null. See Listing 7.13 for the getItemCount()

implementation.

Listing 7.13 Returning the Number of Items

@Override

public int getItemCount() {

return (deviceCursor == null ? 0 : deviceCursor.getCount());

}

Once the number of items in the cursor has been returned, RecyclerView starts to make calls to DeviceCursorAdapter.onBindViewHolder() to populate its view with the data from the adapter. Listing 7.14 shows the implementation of onBindViewHolder().

Listing 7.14 Updating the UI in onBindViewHolder()

@Override

public void onBindViewHolder(DeviceViewHolder holder, int position) { if (deviceCursor != null && deviceCursor.moveToPosition(position)) {

String model = deviceCursor .getString(deviceCursor

.getColumnIndexOrThrow(DevicesContract .DeviceManufacturer

.MODEL));

int deviceId = deviceCursor .getInt(deviceCursor

.getColumnIndexOrThrow(DevicesContract .DeviceManufacturer

.DEVICE_ID));

String shortName = deviceCursor .getString(deviceCursor

.getColumnIndexOrThrow(DevicesContract .DeviceManufacturer

.SHORT_NAME));

holder.name.setText(getString(R.string.device_name,

ptg18221911 Accessing a Content Provider from an Activity 153

shortName, model, deviceId));

holder.uri = ContentUris

.withAppendedId(DevicesContract.Device.CONTENT_URI, deviceId);

} }

The onBindViewHolder() method is passed a DeviceViewHolder which contains the views for the list item in the RecyclerView and a position that is the offset of the current item in the RecyclerView. With this information, onBindViewHolder() first checks to make sure the deviceCursor is not currently null, and if it is not it checks to see if the cursor point can be moved to the required position. If both of these cases are true, onBindViewHolder() reads the cursor information and uses it to populate the views contained in the viewHolder.

For the DeviceListActivity, the ViewHolder contains views for the device model, device ID, and manufacturer’s name. The holder also keeps track of the URI that can be used to retrieve the device from the DeviceContentProvider so that when the user clicks on a device, the URI can be passed to the DeviceDetailsActivity so it can read the device from the database and show the user more information about the device.

Listing 7.15 shows the complete implementation of both the DeviceCursorAdapter

and the DeviceViewHolder classes.

Listing 7.15 Implementing DeviceCursorAdapter and DeviceViewHolder

private class DeviceCursorAdapter

extends RecyclerView.Adapter<DeviceViewHolder> { private Cursor deviceCursor;

@Override

public DeviceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext())

.inflate(R.layout.list_item_device, parent, false);

return new DeviceViewHolder(view);

}

ptg18221911 @Override

public void onBindViewHolder(DeviceViewHolder holder, int position) {

if (deviceCursor != null

&& deviceCursor.moveToPosition(position)) { String model = deviceCursor

.getString(deviceCursor

.getColumnIndexOrThrow(DevicesContract .DeviceManufacturer

.MODEL));

int deviceId = deviceCursor .getInt(deviceCursor

.getColumnIndexOrThrow(DevicesContract .DeviceManufacturer

.DEVICE_ID));

String shortName = deviceCursor .getString(deviceCursor

.getColumnIndexOrThrow(DevicesContract .DeviceManufacturer

.SHORT_NAME));

holder.name.setText(getString(R.string.device_name, shortName,

model, deviceId));

holder.uri = ContentUris

.withAppendedId(DevicesContract.Device.CONTENT_URI, deviceId);

} }

@Override

ptg18221911 Accessing a Content Provider from an Activity 155

public int getItemCount() {

return (deviceCursor == null ? 0 : deviceCursor.getCount());

}

public void swapCursor(Cursor newDeviceCursor) { if (deviceCursor != null) {

deviceCursor.close();

}

deviceCursor = newDeviceCursor;

notifyDataSetChanged();

} }

private class DeviceViewHolder

extends RecyclerView.ViewHolder implements View.OnClickListener { public TextView name;

public Uri uri;

public DeviceViewHolder(View itemView) { super(itemView);

itemView.setOnClickListener(this);

name = (TextView) itemView.findViewById(R.id.name);

}

@Override

public void onClick(View view) { Intent detailIntent =

new Intent(view.getContext(),

DeviceDetailActivity.class);

ptg18221911 detailIntent.putExtra(DeviceDetailActivity.EXTRA_DEVICE_URI, uri);

startActivity(detailIntent);

} }