Giter Site home page Giter Site logo

cursor-utils's Introduction

cursor_utils_logo

Build Status

Working with Android SQLite Cursors is less than ideal. Code can easily be duplicated throughout your codebase if you're not careful to keep the translation of SQLite columns into Java objects. Iterating across the returned data from a Cursor is another area which can lead to code duplication. Sometimes you actually want a Cursor to be a List, yet other times a List to be a Cursor.

This library was designed to encapsulate the repeatable actions for Android Cursors and treat them as if they were closer to a Java object rather than a conglomerate of dicts/hashes.

Here is a presentation from the NYC Android Meetup on August 27, 2014, about the library on SlideShare.

IterableCursor (and IterableCursorWrapper)

At minimum, you should use an IterableCursorWrapper and never look at a plain old Android Cursor again.

public class User {
    public User(String name, String bio) { /* ... */ }
}

public class UserCursor extends IterableCursorWrapper<User> {

    public UserCursor(Cursor c) {
        super(c);
    }

    public User peek() {
        String name = getString(COLUMN_USER_NAME, "Default Name");
        String bio = getLong(COLUMN_USER_BIO, "No bio yet");
        return new User(name, bio);
    }
    
}

public class MyDatabase extends SQLiteOpenHelper {

    // ...
    
    public IterableCursor<User> queryAllUsers() {
        Cursor cursor = getReadableDatabase().query(TABLE_USERS, null, null, null, null, null,
                null, null);
        return new UserCursor(cursor);
    }
    
}

Voilà! Now you can use an enhanced for loop to access all Users at once:

IterableCursor<User> users = myDb.queryAllUsers();
for (User user : users) {
    if (user.isMyBestFriend()) giveSomeCake(user);
}
users.close();

This also simplifies using SQL-backed CursorAdapters (via IterableCursorAdapter<T>:

@Override
public void bindView(View v, Context context, User user) {
    ViewHolder holder = v.getTag();
    holder.textView.setText(user.getFullName());
}

IterableCursorAdapter is a combination of the newView() / bindView() API of CursorAdapter, with the direct item access of ArrayAdapter<T>.

CursorList

Often, certain queries are difficult to express in SQL constraints and it's much simpler to defer to Java to do filtering. But what happens when you're done filtering: you're left with a List but you want to use your favorite CursorAdapter. To solve this issue, mask your List as a CursorList:

IterableCursor<User> users = myDb.queryAllUsers();
List<User> bestFriends = new LinkedList<User>();
for (User user : users) {
    if (user.isMyBestFriend()) bestFriends.add(user);
}

IterableCursor<User> bestFriendCursor = new CursorList<User>(bestFriends);
bestFriendsListView.setAdapter(new UserCursorAdapter(bestFriendsCursor));

CursorList is an instance of both android.database.Cursor and java.util.List; it's the best of both worlds.

IterableCursor + IterableCursor

What if you wanted to have one master ListView that prioritized best friends first, but then listed everyone alphabetically?

List<User> bestFriendsList = computeBestFriendsFromCursor(); // see above
IterableCursor<User> bestFriends = new CursorList(bestFriendsList);
IterableCursor<User> allUsers = myDb.queryAllUsers();

IterableCursor<User> mergedCursor = new IterableMergeCursor<User>(bestFriends, allUsers);
listView.setAdapter(new UserCursorAdapter(mergedCursor));

The adapter has no idea that it's getting two cursors - it still gets to peek() for a User and doesn't need to know that you already did some sorting.

Cursor helper methods

Getting data at a particular column of a Cursor is a pain. And what happens if you have a custom query that only returns certain columns and not the full row?

String field1 = cursor.getString(cursor.getColumnIndex(COLUMN_FIELD1));
int field2 = cursor.getInt(cursor.getColumnIndex(COLUMN_FIELD2));
try {
    boolean field3 = cursor.getBoolean(cursor.getColumnIndex(COLUMN_FIELD3));
} catch (Exception) {
    // oh no, field3 wasn't returned this time! what should I do???
}
// ...

IterableCursorWrapper provides convenience methods which remove this headache with simpler methods and default values.

public User peek() {
    String field1 = getString(COLUMN_FIELD1, "default");
    int field2 = getInteger(COLUMN_FIELD2, 0);
    boolean field3 = getBoolean(COLUMN_FIELD3, DEFAULT_FIELD3_VALUE);
}

Cursor → Collection

If the cursor size isn't too big and you just want to deal with a Collection instead, CursorUtils has some methods to translate the IterableCursor into your favorite collection:

IterableCursor<User> cursor1 = queryAllUsers();
ArrayList<User> all = CursorUtils.consumeToArrayList(cursor1);
// also comes with consumeToLinkedList()

IterableCursor<Tweet> cursor2 = queryAllTweetsAndRetweets();
LinkedHashSet<User> noRepeats = CursorUtils.consumeToLinkedHashSet(cursor2);

IterableCursor<GooglePlusUser> cursor3 = queryComplicatedDataSet();
CoolGuavaCollection<GooglePlusUser> hashMultiQueue = Guava.newAwesome();
CursorUtils.consumeToCollection(hashMultiQueue, cursor3);

Download

Gradle:

compile 'com.venmo.cursor:library:0.4'

Maven:

<dependency>
  <groupId>com.venmo.cursor>
  <artifactId>library</artifactId>
  <version>0.4</version>
</dependency>

Jar: [direct download link]

Contributing

We'd love to see your ideas for improving this library! The best way to contribute is by submitting a pull request – we'll do our best to respond to your patch as soon as possible. You can also submit an issue if you find bugs or have any questions. :octocat:

Please make sure to follow our general coding style and add test coverage for new features!

Contributors

@tpoulos for an awesome logo!

cursor-utils's People

Contributors

arturdryomov avatar ayanonagon avatar kousun12 avatar ronshapiro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cursor-utils's Issues

LoaderCallbacks.onLoaderReset & NPE

I’m resetting cursor at LoaderCallbacks#onLoaderReset as described at the documentation. Unfortunately this action fails for IterableCursorAdapter. Trace is below for cursor-utils:0.3.

Actually I’m not sure why it happens because instance of check should work just fine with null values.

java.lang.RuntimeException: Unable to destroy activity: java.lang.NullPointerException
  at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3273)
  at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3291)
  at android.app.ActivityThread.access$1200(ActivityThread.java:130)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1248)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:137)
  at android.app.ActivityThread.main(ActivityThread.java:4745)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:511)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
  at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
  at com.venmo.cursor.support.IterableCursorAdapter.enforceIterableCursor(IterableCursorAdapter.java:62)
  at com.venmo.cursor.support.IterableCursorAdapter.swapCursor(IterableCursorAdapter.java:44)
  at application.id.fragment.ListFragment.onLoaderReset(ListFragment.java:74)
  at android.support.v4.app.LoaderManagerImpl$LoaderInfo.destroy(LoaderManager.java:339)
  at android.support.v4.app.LoaderManagerImpl.doDestroy(LoaderManager.java:776)
  at android.support.v4.app.Fragment.onDestroy(Fragment.java:1202)
  at android.support.v4.app.Fragment.performDestroy(Fragment.java:2006)
  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1076)
  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1126)
  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1108)
  at android.support.v4.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:1954)
  at android.support.v4.app.FragmentActivity.onDestroy(FragmentActivity.java:313)
  at android.support.v7.app.ActionBarActivity.onDestroy(ActionBarActivity.java:169)
  at android.app.Activity.performDestroy(Activity.java:5172)
  at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1109)
  at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3260)
  at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3291)
  at android.app.ActivityThread.access$1200(ActivityThread.java:130)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1248)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:137)
  at android.app.ActivityThread.main(ActivityThread.java:4745)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:511)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
  at dalvik.system.NativeStart.main(Native Method)

Cursor Wrapper API

Just a suggestion. I think it is pretty safe to get rid of the “helper” postfix for method names. I. e. IterableCursorWrapper#getBooleanHelper can be named as IterableCursorWrapper#getBoolean. There shouldn’t be any conflict with CursorWrapper methods. Also, from the English language perspective probably it makes more sense because now it sounds more like “give me boolean’s helper”. But I’m not native speaker at all ;-)

Provide a version of `getColumnIndex` that doesn't ignore periods in column names

Here is the code for SQLiteCursor in KitKat:

if (mColumnNameMap == null) {
    String[] columns = mColumns;
    int columnCount = columns.length;
    HashMap<String, Integer> map = new HashMap<String, Integer>(columnCount, 1);
    for (int i = 0; i < columnCount; i++) {
        map.put(columns[i], i);
    }
    mColumnNameMap = map;
}

// Hack according to bug 903852
final int periodIndex = columnName.lastIndexOf('.');
if (periodIndex != -1) {
    Exception e = new Exception();
    Log.e(TAG, "requesting column name with table name -- " + columnName, e);
    columnName = columnName.substring(periodIndex + 1);
}

Integer i = mColumnNameMap.get(columnName);
if (i != null) {
    return i.intValue();
} else {
    return -1;
}

From searching on the internet, it appears that "bug 903852" seems to be an internal bug and lots of people wish the workaround didn't exist. Might not make sense to override getColumnIndex since not all cursors are SQLiteCursors, but I think it makes sense to have an option for people that want it.

This would allow something like

select table_name.column_name,other_table.other_column, where ...

Example has a typo...

Just a simple issue. Your example for IterableCursorWrapper in README.md uses getLong for getting CULOUMN_USER_BIO, but you assign it to a string. It's good to show both getString and getLong so I'd just change the example to get something that makes sense as a long value instead of bio and have it return a long.

This is the snippet in error. I've commented the erroneous line...

public class UserCursor extends IterableCursorWrapper<User> {

    public UserCursor(Cursor c) {
        super(c);
    }

    public User peek() {
        String name = getString(COLUMN_USER_NAME, "Default Name");
        // getLong returns a long, not a String. Should update the example with something that's a long value...
//        String bio = getLong(COLUMN_USER_BIO, "No bio yet");
        return new User(name, bio);
    }

}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.