diff --git a/build.gradle b/build.gradle index 53e9d1434..8d2c84b0f 100644 --- a/build.gradle +++ b/build.gradle @@ -101,8 +101,8 @@ ext.libraries = [ recyclerView : 'com.android.support:recyclerview-v7:' + supportLibsVersion, rxAndroid : 'io.reactivex:rxandroid:1.0.1', timber : 'com.jakewharton.timber:timber:3.0.1', - leakCanary : 'com.squareup.leakcanary:leakcanary-android:1.3', - leakCanaryNoOp : 'com.squareup.leakcanary:leakcanary-android-no-op:1.3', + leakCanary : 'com.squareup.leakcanary:leakcanary-android:1.3.1', + leakCanaryNoOp : 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1', autoService : 'com.google.auto.service:auto-service:1.0-rc2', javaPoet : 'com.squareup:javapoet:1.2.0', diff --git a/storio-sample-app/build.gradle b/storio-sample-app/build.gradle index 605759694..81d43cd44 100644 --- a/storio-sample-app/build.gradle +++ b/storio-sample-app/build.gradle @@ -68,4 +68,9 @@ dependencies { debugCompile libraries.leakCanary releaseCompile libraries.leakCanaryNoOp + + testCompile libraries.junit + testCompile libraries.assertJ + testCompile libraries.robolectric + testCompile libraries.leakCanaryNoOp } diff --git a/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/Tweet.java b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/Tweet.java index f89bf8a0f..e621f73bf 100644 --- a/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/Tweet.java +++ b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/Tweet.java @@ -31,10 +31,6 @@ public class Tweet { @StorIOSQLiteColumn(name = TweetsTable.COLUMN_CONTENT) String content; - @NonNull - @StorIOSQLiteColumn(name = "some_bytes") - byte[] someBytes; - // leave default constructor for AutoGenerated code! Tweet() { } diff --git a/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/User.java b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/User.java index ab242b724..e30e532b6 100644 --- a/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/User.java +++ b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/User.java @@ -37,4 +37,14 @@ public static User newUser(@Nullable Long id, @NonNull String nick) { user.nick = nick; return user; } + + @Nullable + public Long id() { + return id; + } + + @NonNull + public String nick() { + return nick; + } } diff --git a/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/UserWithTweets.java b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/UserWithTweets.java new file mode 100644 index 000000000..be8b952f2 --- /dev/null +++ b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/entities/UserWithTweets.java @@ -0,0 +1,44 @@ +package com.pushtorefresh.storio.sample.db.entities; + +import android.support.annotation.NonNull; + +import java.util.List; + +import static java.util.Collections.unmodifiableList; + +/** + * Example of entity with linked sub-entities! + * + * It's a User with his tweets. + * + * Main idea of this example is to show you that + * StorIO can solve ORM problems but still be a DAO. + * + * Moreover, we will write GetResolver in such manner + * that you will be able to write use any Query with it, + * but at the same time you could optimize any frequent case + * with JOIN and other SQL things directly in GetResolver. + */ +public final class UserWithTweets { + + @NonNull + private final User user; + + @NonNull + private final List tweets; + + public UserWithTweets(@NonNull User user, @NonNull List tweets) { + this.user = user; + this.tweets = unmodifiableList(tweets); // We prefer immutable entities + } + + @NonNull + public User user() { + return user; + } + + @NonNull + public List tweets() { + return tweets; + } +} diff --git a/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/resolvers/UserWithTweetsDeleteResolver.java b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/resolvers/UserWithTweetsDeleteResolver.java new file mode 100644 index 000000000..a3bb36316 --- /dev/null +++ b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/resolvers/UserWithTweetsDeleteResolver.java @@ -0,0 +1,42 @@ +package com.pushtorefresh.storio.sample.db.resolvers; + +import android.support.annotation.NonNull; + +import com.pushtorefresh.storio.sample.db.entities.UserWithTweets; +import com.pushtorefresh.storio.sample.db.tables.TweetsTable; +import com.pushtorefresh.storio.sample.db.tables.UsersTable; +import com.pushtorefresh.storio.sqlite.StorIOSQLite; +import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResolver; +import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResult; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public final class UserWithTweetsDeleteResolver extends DeleteResolver { + + @NonNull + @Override + public DeleteResult performDelete(@NonNull StorIOSQLite storIOSQLite, @NonNull UserWithTweets userWithTweets) { + // 1 for user and other for his/her tweets + final List objectsToDelete = new ArrayList(1 + userWithTweets.tweets().size()); + + objectsToDelete.add(userWithTweets.user()); + objectsToDelete.addAll(userWithTweets.tweets()); + + storIOSQLite + .delete() + .objects(objectsToDelete) + .prepare() + .executeAsBlocking(); + + // BTW, you can save it as static final + final Set affectedTables = new HashSet(2); + + affectedTables.add(UsersTable.TABLE); + affectedTables.add(TweetsTable.TABLE); + + return DeleteResult.newInstance(objectsToDelete.size(), affectedTables); + } +} diff --git a/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/resolvers/UserWithTweetsGetResolver.java b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/resolvers/UserWithTweetsGetResolver.java new file mode 100644 index 000000000..6cc386dd0 --- /dev/null +++ b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/resolvers/UserWithTweetsGetResolver.java @@ -0,0 +1,82 @@ +package com.pushtorefresh.storio.sample.db.resolvers; + +import android.database.Cursor; +import android.support.annotation.NonNull; + +import com.pushtorefresh.storio.sample.db.entities.Tweet; +import com.pushtorefresh.storio.sample.db.entities.User; +import com.pushtorefresh.storio.sample.db.entities.UserWithTweets; +import com.pushtorefresh.storio.sample.db.tables.TweetsTable; +import com.pushtorefresh.storio.sqlite.StorIOSQLite; +import com.pushtorefresh.storio.sqlite.operations.get.GetResolver; +import com.pushtorefresh.storio.sqlite.queries.Query; +import com.pushtorefresh.storio.sqlite.queries.RawQuery; + +import java.util.List; + +public final class UserWithTweetsGetResolver extends GetResolver { + + // We can even reuse existing get resolvers for our needs + // But, you can always write custom code, of course. + @NonNull + private final GetResolver userGetResolver; + + // Sorry for this hack :( + // We will pass you an instance of StorIO + // into the mapFromCursor() in v2.0.0. + // + // At the moment, you can save this instance in performGet() and then null it at the end + @NonNull + private final ThreadLocal storIOSQLiteFromPerformGet = new ThreadLocal(); + + public UserWithTweetsGetResolver(@NonNull GetResolver userGetResolver) { + this.userGetResolver = userGetResolver; + } + + @NonNull + @Override + public UserWithTweets mapFromCursor(@NonNull Cursor cursor) { + final StorIOSQLite storIOSQLite = storIOSQLiteFromPerformGet.get(); + + // BTW, you don't need a transaction here + // StorIO will wrap mapFromCursor() into the transaction if needed + try { + // Or you can manually parse cursor (it will be sliiightly faster) + final User user = userGetResolver.mapFromCursor(cursor); + + // Yep, you can reuse StorIO here! + // Or, you can do manual low level requests here + // BTW, if you profiled your app and found that such queries are not very fast + // You can always add some optimized version for particular queries to improve the performance + final List tweetsOfTheUser = storIOSQLite + .get() + .listOfObjects(Tweet.class) + .withQuery(Query.builder() + .table(TweetsTable.TABLE) + .where(TweetsTable.COLUMN_AUTHOR + "=?") + .whereArgs(user.nick()) + .build()) + .prepare() + .executeAsBlocking(); + + return new UserWithTweets(user, tweetsOfTheUser); + } finally { + // Releasing StorIOSQLite reference + storIOSQLiteFromPerformGet.set(null); + } + } + + @NonNull + @Override + public Cursor performGet(@NonNull StorIOSQLite storIOSQLite, @NonNull RawQuery rawQuery) { + storIOSQLiteFromPerformGet.set(storIOSQLite); + return storIOSQLite.internal().rawQuery(rawQuery); + } + + @NonNull + @Override + public Cursor performGet(@NonNull StorIOSQLite storIOSQLite, @NonNull Query query) { + storIOSQLiteFromPerformGet.set(storIOSQLite); + return storIOSQLite.internal().query(query); + } +} diff --git a/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/resolvers/UserWithTweetsPutResolver.java b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/resolvers/UserWithTweetsPutResolver.java new file mode 100644 index 000000000..643160c3a --- /dev/null +++ b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/resolvers/UserWithTweetsPutResolver.java @@ -0,0 +1,43 @@ +package com.pushtorefresh.storio.sample.db.resolvers; + +import android.support.annotation.NonNull; + +import com.pushtorefresh.storio.sample.db.entities.UserWithTweets; +import com.pushtorefresh.storio.sample.db.tables.TweetsTable; +import com.pushtorefresh.storio.sample.db.tables.UsersTable; +import com.pushtorefresh.storio.sqlite.StorIOSQLite; +import com.pushtorefresh.storio.sqlite.operations.put.PutResolver; +import com.pushtorefresh.storio.sqlite.operations.put.PutResult; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public final class UserWithTweetsPutResolver extends PutResolver { + + @NonNull + @Override + public PutResult performPut(@NonNull StorIOSQLite storIOSQLite, @NonNull UserWithTweets userWithTweets) { + // 1 for user and other for his/her tweets + final List objectsToPut = new ArrayList(1 + userWithTweets.tweets().size()); + + objectsToPut.add(userWithTweets.user()); + objectsToPut.addAll(userWithTweets.tweets()); + + storIOSQLite + .put() + .objects(objectsToPut) + .prepare() + .executeAsBlocking(); + + // BTW, you can save it as static final + final Set affectedTables = new HashSet(2); + + affectedTables.add(UsersTable.TABLE); + affectedTables.add(TweetsTable.TABLE); + + // Actually, we don't know what to return for such complex operation, so let's return update result + return PutResult.newUpdateResult(objectsToPut.size(), affectedTables); + } +} diff --git a/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/tables/TweetsTable.java b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/tables/TweetsTable.java index 85e043a77..3e98a735d 100644 --- a/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/tables/TweetsTable.java +++ b/storio-sample-app/src/main/java/com/pushtorefresh/storio/sample/db/tables/TweetsTable.java @@ -26,8 +26,6 @@ public class TweetsTable { @NonNull public static final String COLUMN_CONTENT = "content"; - public static final String COLUMN_SOME_BYTES = "some_bytes"; - // Yep, with StorIO you can safely store queries as objects and reuse them, they are immutable @NonNull public static final Query QUERY_ALL = Query.builder() @@ -46,8 +44,7 @@ public static String getCreateTableQuery() { return "CREATE TABLE " + TABLE + "(" + COLUMN_ID + " INTEGER NOT NULL PRIMARY KEY, " + COLUMN_AUTHOR + " TEXT NOT NULL, " - + COLUMN_CONTENT + " TEXT NOT NULL," - + COLUMN_SOME_BYTES + " BLOB " + + COLUMN_CONTENT + " TEXT NOT NULL" + ");"; } } diff --git a/storio-sample-app/src/test/java/com/pushtorefresh/storio/sample/db/DbOpenHelperTest.java b/storio-sample-app/src/test/java/com/pushtorefresh/storio/sample/db/DbOpenHelperTest.java new file mode 100644 index 000000000..d001abc5a --- /dev/null +++ b/storio-sample-app/src/test/java/com/pushtorefresh/storio/sample/db/DbOpenHelperTest.java @@ -0,0 +1,26 @@ +package com.pushtorefresh.storio.sample.db; + +import android.database.sqlite.SQLiteDatabase; + +import com.pushtorefresh.storio.contentresolver.BuildConfig; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(RobolectricGradleTestRunner.class) +@Config(constants = BuildConfig.class, sdk = 21) +public final class DbOpenHelperTest { + + @Test + public void shouldCreateDb() { + SQLiteDatabase database = new DbOpenHelper(RuntimeEnvironment.application) + .getWritableDatabase(); + + assertThat(database).isNotNull(); + } +}