#####Overview:
- Powerful set of Operations:
Put
,Get
,Delete
- Convenient builders. Forget about 6-7
null
in queries - No reflection, no annotations,
StorIO
is not ORM - Every Operation over
StorIO
can be executed as blocking call or asrx.Observable
RxJava
as first class citizen, but it's not required dependency!Observable
fromGet
Operation can observe changes inStorIO
and receive updates automatically- If you don't want to work with
Cursor
andContentValue
you don't have to StorIO
can replaceLoaders
StorIO
is mockable for testing
###StorIOSQLiteDb — API for SQLite Database
####0. Create an instance of StorIOSQLiteDb
StorIOSQLiteDb storIOSQLiteDb = new StorIOSQLiteDbImpl.Builder()
.sqliteOpenHelper(yourSqliteOpenHelper) // or .db(db)
.build();
It's a good practice to use one instance of StorIOSQLiteDb
per database.
####1. Get Operation ######Get list of objects with blocking call:
// it's a good practice to store MapFunc as public static final field somewhere
final MapFunc<Cursor, Tweet> mapFunc = new MapFunc<Cursor, Tweet>() {
@Override public Tweet map(Cursor cursor) {
// no need to move cursor and close it, StorIO will handle it for you
return new Tweet(); // fill with values from cursor
}
};
final List<Tweet> tweets = storIOSQLiteDb
.get()
.listOfObjects(Tweet.class)
.withMapFunc(mapFunc)
.withQuery(new Query.Builder()
.table("tweets")
.build())
.prepare()
.executeAsBlocking();
######Get Cursor
via blocking call:
final Cursor tweetsCursor = storIOSQLiteDb
.get()
.cursor()
.withQuery(new Query.Builder()
.table("tweets")
.build())
.prepare()
.executeAsBlocking();
Things become much more interesting with RxJava
!
######Get cursor as Observable
storIOSQLiteDb
.get()
.cursor()
.withQuery(new Query.Builder()
.table("tweets")
.build())
.prepare()
.createObservable()
.subscribeOn(Schedulers.io()) // moving Get Operation to other thread
.observeOn(AndroidSchedulers.mainThread()) // observing result on Main thread
.subscribe(new Action1<Cursor>() {
@Override public void call(Cursor cursor) {
// display the data from cursor
// will be called once
}
});
#####What if you want to observe changes in StorIOSQLiteDb
?
######First-case: Receive updates to Observable
on each change in tables from Query
storIOSQLiteDb
.get()
.listOfObjects(Tweet.class)
.withMapFunc(Tweet.MAP_FROM_CURSOR)
.withQuery(Tweet.ALL_TWEETS_QUERY)
.prepare()
.createObservableStream() // here is the magic! It will be subscribed to changes in tables from Query
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<Tweet>>() {
@Override public void call(List<Tweet> tweets) {
// display the data
// magic: this will be called on each update in "tweets" table
}
});
// don't forget to manage Subscription and unsubscribe in lifecycle methods to prevent memory leaks
######Second case: Handle changes manually
storIOSQLiteDb
.observeChangesInTable("tweets")
.subscribe(new Action1<Changes>() { // or apply RxJava Operators
// do what you want!
});
######Get result with RawQuery with joins and other SQL things
storIOSQLiteDb
.get()
.listOfObjects(TweetAndUser.class)
.withMapFunc(TweetAndUser.MAP_FROM_CURSOR)
.withQuery(new RawQuery.Builder()
.query("SELECT * FROM tweets JOIN users ON tweets.user_name = users.name WHERE tweets.user_name = ?")
.args("artem_zin")
.build())
.prepare()
.createObservableStream();
######Customize behavior of Get
Operation with GetResolver
GetResolver getResolver = new GetResolver() {
// Performs Get for RawQuery
@Override @NonNull public Cursor performGet(@NonNull StorIOSQLiteDb storIOSQLiteDb, @NonNull RawQuery rawQuery) {
Cursor cursor = ...; // get result as you want, or add some additional behavior
return cursor;
}
// Performs Get for Query
@Override @NonNull public Cursor performGet(@NonNull StorIOSQLiteDb storIOSQLiteDb, @NonNull Query query) {
Cursor cursor = ...; // get result as you want, or add some additional behavior
return cursor;
}
};
storIOSQLiteDb
.get()
.listOfObjects(Tweet.class)
.withMapFunc(Tweet.MAP_FROM_CURSOR)
.withQuery(Tweet.ALL_TWEETS_QUERY)
.withGetResolver(getResolver) // here we set custom GetResolver for Get Operation
.prepare()
.executeAsBlocking();
Several things about Get
Operation:
- There is
DefaultGetResolver
which simply redirects query toStorIOSQLiteDb
,Get
Operation will useDefaultGetResolver
if you won't pass yourGetResolver
, in 99% of casesDefaultGetResolver
will be enough - As you can see, results of
Get
Operation computed even if you'll applyRxJava
operators such asDebounce
, if you want to avoid unneeded computatations, please combineStorIOSQLiteDb.observeChangesInTable()
withGet
Operation manually. - In
StorIO 1.1.0
we are going to addLazy<T>
to allow you skip unneeded computations - If you want to
Put
multiple items intoStorIOSQLiteDb
, better to do this in transaction to avoid multiple calls to the listeners (see docs aboutPut
Operation)
####2. Put Operation
Put
Operation requires PutResolver
which defines the behavior of Put
Operation (insert or update).
You have two ways of implementing PutResolver
:
-
Easy: extend
DefaultPutResolver
and implement it correctlyDefaultPutResolver
will search for field_id
inContentValues
and will perform insert if there is no value or update if there_id
is not null. -
Implement
PutResolver
and perform put as you need.
In 99% of cases your tables have _id
column as unique id and DefaultPutResolver
will be enough
public static final PutResolver<Tweet> PUT_RESOLVER = new DefaultPutResolver<>() {
@Override @NonNull protected String getTable() {
return "tweets";
}
@Override public void afterPut(@NonNull Tweet tweet, @NonNull PutResult putResult) {
// callback were you can change object after insert
if (putResult.wasInserted()) {
tweet.setId(putResult.getInsertedId());
}
}
};
######Put object of some type
Tweet tweet = getSomeTweet();
storIOSQLiteDb
.put()
.object(tweet)
.withMapFunc(Tweet.MAP_TO_CONTENT_VALUES)
.withPutResolver(Tweet.PUT_RESOLVER)
.prepare()
.executeAsBlocking(); // or createObservable()
######Put multiple objects of some type
List<Tweet> tweets = getSomeTweets();
storIOSQLiteDb
.put()
.objects(tweets)
.withMapFunc(Tweet.MAP_TO_CONTENT_VALUES)
.withPutResolver(Tweet.PUT_RESOLVER)
.prepare()
.executeAsBlocking(); // or createObservable()
######Put ContentValues
ContentValues contentValues = getSomeContentValues();
storIOSQLiteDb
.put()
.contentValues(contentValues)
.withPutResolver(putResolver)
.prepare()
.executeAsBlocking(); // or createObservable()
Several things about Put
Operation:
Put
Operation requiresPutResolver
,StorIO
requires it to avoid reflectionPut
Operation can be executed in transaction and by default it will use transaction, you can customize this viauseTransactionIfPossible()
ordontUseTransaction()
Put
Operation in transaction will produce only one notification toStorIOSQLiteDb
observers- Result of
Put
Operation can be useful if you want to know what happened: insert (and insertedId) or update (and number of updated rows)
####3. Delete Operation ######Delete object
// you can store it as static final field somewhere
final MapFunc<Tweet, DeleteQuery> mapToDeleteQuery = new MapFunc<Tweet, DeleteQuery>() {
@Override public DeleteQuery map(Tweet tweet) {
return new DeleteQuery.Builder()
.table(Tweet.TABLE)
.where(Tweet.COLUMN_ID)
.whereArgs(String.valueOf(tweet.getId()))
.build();
}
};
Tweet tweet = getSomeTweet();
storIOSQLiteDb
.delete()
.object(tweet)
.withMapFunc(mapToDeleteQuery)
.prepare()
.executeAsBlocking(); // or createObservable()
######Delete multiple objects
List<Tweet> tweets = getSomeTweets();
storIOSQLiteDb
.delete()
.objects(tweets)
.withMapFunc(mapToDeleteQuery)
.prepare()
.executeAsBlocking(); // or createObservable()
Several things about Delete
Operation:
Delete
Operation of multiple items can be performed in transaction, by default it will use transaction if possible- Same rules as for
Put
Operation about notifications forStorIOSQLiteDb
observers: transaction -> one notification, without transaction - multiple notifications - Result of
Delete
Operation can be useful if you want to know what happened
####4. ExecSql Operation
Sometimes you need to execute raw sql, StorIOSQLiteDb
allows you to do it
storIOSQLiteDb
.execSql()
.withQuery(new RawQuery.Builder()
.query("ALTER TABLE ? ADD COLUMN ? INTEGER")
.args("tweets", "number_of_retweets")
.affectedTables("tweets") // optional: you can specify affected tables to notify Observers
.build())
.prepare()
.executeAsBlocking(); // or createObservable()
Several things about ExecSql
:
- Use it for non insert/update/query/delete operations
- Notice that you can set list of tables that will be affected by
RawQuery
andStorIOSQLiteDb
will notify tables Observers
For more examples, please check our Design Tests
:
####Architecture:
StorIOSQLiteDb
and StorIOContentProvider
— are abstractions with default implementations: StorIOSQLiteDbImpl
and StorIOContentProviderImpl
.
It means, that you can have your own implementation of StorIOSQLiteDb
and StorIOContentProvider
with custom behavior, such as memory caching, verbose logging and so on.
One of the main goals of StorIO
— clean API which will be easy to use and understand, that's why StorIOSQLiteDb
and StorIOContentProvider
have just several methods, but we understand that sometimes you need to go under the hood and StorIO
allows you to do it: StorIOSQLiteDb.Internal
and StorIOContentProvider.Internal
encapsulates low-level methods, you can use them if you need, but please try to avoid it.
####Queries
All Query
objects are immutable, you can share them safely.
####Concept of Prepared Operations
You may notice that each Operation (Get, Put, Delete) should be prepared with prepare()
. StorIO
has an entity called PreparedOperation<T>
, and you can use them to perform group execution of several Prepared Operations or provide PreparedOperation<T>
as a return type of your API (for example in Model layer) and client will decide how to execute it: executeAsBlocking()
or createObservable()
or createObservableStream()
(if possible). Also, Prepared Operations might be useful for ORMs based on StorIO
.
You can customize behavior of every Operation via Resolvers
: GetResolver
, PutResolver
, DeleteResolver
.
Made with love in Pushtorefresh.com by @artem_zin and @nikitin-da