Reading from the Database

Reading in data from SQLite is done using the query(…) method. SQLiteDatabase.query(…) has quite a lot going on. There are a few different overloads of this method. The one you will be using looks like this:

public Cursor query(
    String table,
    String[] columns,
    String where,
    String[] whereArgs,
    String groupBy,
    String having,
    String orderBy,
    String limit)

If you have dealt with SQL before, then most of these will be familiar to you as arguments of the select statement. If you have not, then you only need to worry about the ones you will be using:

public Cursor query(
    String table,
    String[] columns,
    String where,
    String[] whereArgs,
    String groupBy,
    String having,
    String orderBy,
    String limit)

The table argument is the table to query. The columns argument names which columns you want values for and what order you want to receive them in. And then where and whereArgs do the same thing they do in update(…).

Use query(…) in a convenience method to call this on your CrimeTable.

Listing 14.12  Querying for Crimes (CrimeLab.java)

public void updateCrime(Crime crime) {
    ...
}

private Cursor queryCrimes(String whereClause, String[] whereArgs) {
    Cursor cursor = mDatabase.query(
            CrimeTable.NAME,
            null, // columns - null selects all columns
            whereClause,
            whereArgs,
            null, // groupBy
            null, // having
            null  // orderBy
    );

    return cursor;
}

Using a CursorWrapper

A Cursor leaves a lot to be desired as a way to look at a table. All it does is give you raw column values. Pulling data out of a Cursor looks like this:

String uuidString = cursor.getString(
    cursor.getColumnIndex(CrimeTable.Cols.UUID));
String title = cursor.getString(
    cursor.getColumnIndex(CrimeTable.Cols.TITLE));
long date = cursor.getLong(
    cursor.getColumnIndex(CrimeTable.Cols.DATE));
int isSolved = cursor.getInt(
    cursor.getColumnIndex(CrimeTable.Cols.SOLVED));

Every time you pull a Crime out of a cursor, you need to write this code one more time. (And that does not include the code to create a Crime instance with those values!)

Remember the DRY rule of thumb: Don’t repeat yourself. Instead of writing this code each time you need to read data from a Cursor, you can create your own Cursor subclass that takes care of this in one place. The easiest way to write a Cursor subclass is to use CursorWrapper. A CursorWrapper lets you wrap a Cursor you received from another place and add new methods on top of it.

Create a new class in the database package called CrimeCursorWrapper.

Listing 14.13  Creating CrimeCursorWrapper (CrimeCursorWrapper.java)

public class CrimeCursorWrapper extends CursorWrapper {
    public CrimeCursorWrapper(Cursor cursor) {
        super(cursor);
    }
}

That creates a thin wrapper around a Cursor. It has all the same methods as the Cursor it wraps, and calling those methods does the exact same thing. This would be pointless, except that it makes it possible to add new methods that operate on the underlying Cursor.

Add a getCrime() method that pulls out relevant column data. (Remember to use the two-step import trick for CrimeTable here, as you did earlier.)

Listing 14.14  Adding getCrime() method (CrimeCursorWrapper.java)

public class CrimeCursorWrapper extends CursorWrapper {
    public CrimeCursorWrapper(Cursor cursor) {
        super(cursor);
    }

    public Crime getCrime() {
        String uuidString = getString(getColumnIndex(CrimeTable.Cols.UUID));
        String title = getString(getColumnIndex(CrimeTable.Cols.TITLE));
        long date = getLong(getColumnIndex(CrimeTable.Cols.DATE));
        int isSolved = getInt(getColumnIndex(CrimeTable.Cols.SOLVED));

        return null;
    }
}

You will need to return a Crime with an appropriate UUID from this method. Add another constructor to Crime to do this.

Listing 14.15  Adding Crime constructor (Crime.java)

public Crime() {
    this(UUID.randomUUID());
    mId = UUID.randomUUID();
    mDate = new Date();
}

public Crime(UUID id) {
    mId = id;
    mDate = new Date();
}

And then finish up getCrime().

Listing 14.16  Finishing up getCrime() (CrimeCursorWrapper.java)

public Crime getCrime() {
    String uuidString = getString(getColumnIndex(CrimeTable.Cols.UUID));
    String title = getString(getColumnIndex(CrimeTable.Cols.TITLE));
    long date = getLong(getColumnIndex(CrimeTable.Cols.DATE));
    int isSolved = getInt(getColumnIndex(CrimeTable.Cols.SOLVED));

    Crime crime = new Crime(UUID.fromString(uuidString));
    crime.setTitle(title);
    crime.setDate(new Date(date));
    crime.setSolved(isSolved != 0);

    return crime;
    return null;
}

(Android Studio will ask you to choose between java.util.Date and java.sql.Date. Even though you are dealing with databases, java.util.Date is the right choice here.)

Converting to model objects

With CrimeCursorWrapper, vending out a List<Crime> from CrimeLab will be straightforward. You need to wrap the cursor you get back from your query in a CrimeCursorWrapper, then iterate over it calling getCrime() to pull out its Crimes.

For the first part, update queryCrimes(…) to use CrimeCursorWrapper.

Listing 14.17  Vending cursor wrapper (CrimeLab.java)

private Cursor queryCrimes(String whereClause, String[] whereArgs) {
private CrimeCursorWrapper queryCrimes(String whereClause, String[] whereArgs) {
    Cursor cursor = mDatabase.query(
            CrimeTable.NAME,
            null, // columns - null selects all columns
            whereClause,
            whereArgs,
            null, // groupBy
            null, // having
            null  // orderBy
    );

    return cursor;
    return new CrimeCursorWrapper(cursor);
}

Then get getCrimes() into shape. Add code to query for all crimes, walk the cursor, and populate a Crime list.

Listing 14.18  Returning crime list (CrimeLab.java)

public List<Crime> getCrimes() {
    return new ArrayList<>();
    List<Crime> crimes = new ArrayList<>();

    CrimeCursorWrapper cursor = queryCrimes(null, null);

    try {
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            crimes.add(cursor.getCrime());
            cursor.moveToNext();
        }
    } finally {
        cursor.close();
    }

    return crimes;
}

Database cursors are called cursors because they always have their finger on a particular place in a query. So to pull the data out of a cursor, you move it to the first element by calling moveToFirst(), and then read in row data. Each time you want to advance to a new row, you call moveToNext(), until finally isAfterLast() tells you that your pointer is off the end of the data set.

The last important thing to do is to call close() on your Cursor. This bit of housekeeping is important. If you do not do it, your Android device will spit out nasty error logs to berate you. Even worse, if you make a habit out of it, you will eventually run out of open file handles and crash your app. So: Close your cursors.

CrimeLab.getCrime(UUID) will look similar to getCrimes(), except it will only need to pull the first item, if it is there.

Listing 14.19  Rewriting getCrime(UUID) (CrimeLab.java)

public Crime getCrime(UUID id) {
    return null;
    CrimeCursorWrapper cursor = queryCrimes(
            CrimeTable.Cols.UUID + " = ?",
            new String[] { id.toString() }
    );

    try {
        if (cursor.getCount() == 0) {
            return null;
        }

        cursor.moveToFirst();
        return cursor.getCrime();
    } finally {
        cursor.close();
    }
}

That completes a few moving pieces:

  • You can insert crimes, so the code that adds Crime to CrimeLab when you press the New Crime action item now works.

  • You can successfully query the database, so CrimePagerActivity can see all the Crimes in CrimeLab, too.

  • CrimeLab.getCrime(UUID) works, too, so each CrimeFragment displayed in CrimePagerActivity is showing the real Crime.

Now you should be able to press New Crime and see the new Crime displayed in CrimePagerActivity. Run CriminalIntent and verify that you can do this. If you cannot, double-check your implementations from this chapter so far.

Refreshing model data

You are not quite done. Your crimes are persistently stored to the database, but the persistent data is not read back in. So if you press the Back button after editing your new Crime, it will not show up in CrimeListActivity.

This is because CrimeLab now works a little differently. Before, there was only one List<Crime> and one object for each Crime: the one in the List<Crime>. That was because mCrimes was the only authority for which Crimes your app knew about.

Things have changed now. mCrimes is gone. So the List<Crime> returned by getCrimes() is a snapshot of the Crimes at one point in time. To refresh CrimeListActivity, you need to update that snapshot.

Most of the moving pieces to do this are already in place. CrimeListActivity already calls updateUI() to refresh other parts of its interface. All you need to do is have it refresh its view of CrimeLab, too.

First, add a setCrimes(List<Crime>) method to CrimeAdapter to swap out the crimes it displays.

Listing 14.20  Adding setCrimes(List<Crime>) (CrimeListFragment.java)

private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {
    ...
    @Override
    public int getItemCount() {
        return mCrimes.size();
    }

    public void setCrimes(List<Crime> crimes) {
        mCrimes = crimes;
    }
}

Then call setCrimes(List<Crime>) in updateUI().

Listing 14.21  Calling setCrimes(List<>) (CrimeListFragment.java)

private void updateUI() {
    CrimeLab crimeLab = CrimeLab.get(getActivity());
    List<Crime> crimes = crimeLab.getCrimes();

    if (mAdapter == null) {
        mAdapter = new CrimeAdapter(crimes);
        mCrimeRecyclerView.setAdapter(mAdapter);
    } else {
        mAdapter.setCrimes(crimes);
        mAdapter.notifyDataSetChanged();
    }

    updateSubtitle();
}

Now everything should work correctly. Run CriminalIntent and verify that you can add a crime, press the Back button, and see that crime in CrimeListActivity.

This is also a good time to test that calls to updateCrime(Crime) in CrimeFragment work, too. Press a Crime and edit its title inside CrimePagerActivity. Press the Back button and make sure that the new title is reflected in the list.

..................Content has been hidden....................

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