table) {
36 | mTable = table;
37 | }
38 |
39 | @Override
40 | protected void subscribeActual(Observer super Boolean> observer) {
41 | TableListener tableListener = new TableListener(observer);
42 | observer.onSubscribe(tableListener);
43 | SQLite.get().registerObserver(mTable, tableListener);
44 | }
45 |
46 | /**
47 | * This method transforms notifications observable to the observable with list of all objects in the table.
48 | *
49 | * It also works in the background.
50 | *
51 | * @return observable with all elements from the table
52 | */
53 | @NonNull
54 | public Observable> withQuery() {
55 | return withQuery(Where.create());
56 | }
57 |
58 | /**
59 | * This method transforms notifications observable to the observable
60 | * with list of all objects in the table which satisfies where parameter
61 | *
62 | * It also works in the background.
63 | *
64 | * @param where - arguments to query table
65 | * @return observable with all elements from the table
66 | */
67 | @NonNull
68 | public Observable> withQuery(@NonNull final Where where) {
69 | return flatMap(new Function>>() {
70 | @Override
71 | public ObservableSource> apply(Boolean value) throws Exception {
72 | return RxSQLite.get().query(mTable, where);
73 | }
74 | }).subscribeOn(Schedulers.io());
75 | }
76 |
77 | private class TableListener extends MainThreadDisposable implements BasicTableObserver {
78 |
79 | private final Observer super Boolean> mObserver;
80 |
81 | TableListener(@NonNull Observer super Boolean> observer) {
82 | mObserver = observer;
83 | }
84 |
85 | @Override
86 | public void onTableChanged() {
87 | mObserver.onNext(true);
88 | }
89 |
90 | @Override
91 | protected void onDispose() {
92 | SQLite.get().unregisterObserver(this);
93 | }
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/arturvasilov/sqlite/core/Observers.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.core;
2 |
3 | import android.content.Context;
4 | import android.database.ContentObserver;
5 | import android.support.annotation.NonNull;
6 | import android.support.v4.util.Pair;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import io.reactivex.android.schedulers.AndroidSchedulers;
12 | import io.reactivex.functions.Consumer;
13 | import io.reactivex.schedulers.Schedulers;
14 | import ru.arturvasilov.sqlite.rx.RxSQLite;
15 |
16 | /**
17 | * @author Artur Vasilov
18 | */
19 | final class Observers {
20 |
21 | private final List> mObservers = new ArrayList<>();
22 |
23 | public void registerObserver(@NonNull Context context, @NonNull Table table,
24 | @NonNull final BasicTableObserver observer) {
25 | ContentObserver contentObserver = new DatabaseObserver() {
26 | @Override
27 | public void onChange(boolean selfChange) {
28 | super.onChange(selfChange);
29 | MainHandler.getHandler().post(new Runnable() {
30 | @Override
31 | public void run() {
32 | observer.onTableChanged();
33 | }
34 | });
35 | }
36 | };
37 | context.getContentResolver().registerContentObserver(table.getUri(), false, contentObserver);
38 | mObservers.add(new Pair(observer, contentObserver));
39 | }
40 |
41 | public void registerObserver(@NonNull Context context, @NonNull final Table table,
42 | @NonNull final ContentTableObserver observer, @NonNull final Where where) {
43 | ContentObserver contentObserver = new DatabaseObserver() {
44 | @Override
45 | public void onChange(boolean selfChange) {
46 | super.onChange(selfChange);
47 | RxSQLite.get().query(table, where)
48 | .subscribeOn(Schedulers.io())
49 | .observeOn(AndroidSchedulers.mainThread())
50 | .subscribe(new Consumer>() {
51 | @Override
52 | public void accept(List list) throws Exception {
53 | observer.onTableChanged(list);
54 | }
55 | });
56 | }
57 | };
58 | context.getContentResolver().registerContentObserver(table.getUri(), false, contentObserver);
59 | mObservers.add(new Pair(observer, contentObserver));
60 | }
61 |
62 | public void unregisterObserver(@NonNull Context context, @NonNull BasicTableObserver observer) {
63 | unregisterObserver(context, (Object) observer);
64 | }
65 |
66 | public void unregisterObserver(@NonNull Context context, @NonNull ContentTableObserver observer) {
67 | unregisterObserver(context, (Object) observer);
68 | }
69 |
70 | private void unregisterObserver(@NonNull Context context, @NonNull Object object) {
71 | int index = -1;
72 | for (int i = 0; i < mObservers.size(); i++) {
73 | if (mObservers.get(i).first == object) {
74 | index = i;
75 | }
76 | }
77 |
78 | if (index >= 0) {
79 | context.getContentResolver().unregisterContentObserver(mObservers.get(index).second);
80 | mObservers.remove(index);
81 | }
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/arturvasilov/sqlite/core/Table.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.core;
2 |
3 | import android.content.ContentValues;
4 | import android.database.Cursor;
5 | import android.net.Uri;
6 | import android.support.annotation.NonNull;
7 |
8 | import org.sqlite.database.sqlite.SQLiteDatabase;
9 |
10 | /**
11 | * Interface for creating a single table in SQLite database.
12 | *
13 | * You normally won't implement this interface but extend {@link BaseTable},
14 | * which provides good enough implementations for most of the methods.
15 | *
16 | * @author Artur Vasilov
17 | */
18 | public interface Table {
19 |
20 | /**
21 | * Each table is registered in ContentProvider with Uri from this method.
22 | * Default implementation returns ContentProvider uri appended with table name.
23 | *
24 | * @return Uri for table in database
25 | */
26 | @NonNull
27 | Uri getUri();
28 |
29 | /**
30 | * This method is used for creating and dropping table in database
31 | * and in the default implementation for {@link Table#getUri()}
32 | *
33 | * Default implementation returns simple class name (e.g. UsersTable)
34 | *
35 | * @return name of the table in SQLite database
36 | */
37 | @NonNull
38 | String getTableName();
39 |
40 | /**
41 | * This method creates and adds table to the database.
42 | *
43 | * You can create it manually with SQL, but it's recommended to use {@link ru.arturvasilov.sqlite.utils.TableBuilder}
44 | *
45 | * @param database - instance of SQLite database where you should create the table
46 | */
47 | void onCreate(@NonNull SQLiteDatabase database);
48 |
49 | /**
50 | * This method is provided for data migration purposes.
51 | *
52 | * In this method you should decide how you want to update this table
53 | * (how data migration works - {@link Table#getLastUpgradeVersion()}
54 | *
55 | * Default implementation in {@link BaseTable} simple drops table and call {@link Table#onCreate(SQLiteDatabase)}
56 | *
57 | * @param database - instance of SQLite database where you should update the table
58 | */
59 | void onUpgrade(@NonNull SQLiteDatabase database);
60 |
61 | /**
62 | * This method is also provided for data migration purposes.
63 | *
64 | * Version of database is calculated as a maximum from all tables in {@link SQLiteSchema}.
65 | * When you have updated the table, you just have to override this method to return maximum value from all tables
66 | * (e.g. current version of database is 5, you overridden this method to return 6 - database version will be six
67 | * and {@link Table#onUpgrade(SQLiteDatabase)} will be called)
68 | * If this method returns value which is less than updated database version,
69 | * {@link Table#onUpgrade(SQLiteDatabase)} won't be called.
70 | *
71 | * @return the version of database where the table was lastly updated
72 | */
73 | int getLastUpgradeVersion();
74 |
75 | /**
76 | * @param t - object for this table which you need to convert to {@link ContentValues} to insert in to database
77 | * @return ContentValues instance for object
78 | */
79 | @NonNull
80 | ContentValues toValues(@NonNull T t);
81 |
82 | /**
83 | * In this method you need to create instance of class for this table from cursor.
84 | * It's guarantee that cursor at the right position and its' state is OK.
85 | *
86 | * @param cursor - cursor (opened and ready to be read)
87 | * @return instance of class for this table
88 | */
89 | @NonNull
90 | T fromCursor(@NonNull Cursor cursor);
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/arturvasilov/sqlite/utils/TableBuilder.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.utils;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.VisibleForTesting;
5 |
6 | import org.sqlite.database.sqlite.SQLiteDatabase;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import ru.arturvasilov.sqlite.core.Table;
12 |
13 | /**
14 | * @author Artur Vasilov
15 | */
16 | public final class TableBuilder {
17 |
18 | private String mTableName;
19 | private final List mPrimaryKeys;
20 | private final List mIntegerColumns;
21 | private final List mRealColumns;
22 | private final List mTextColumns;
23 |
24 | private TableBuilder() {
25 | mPrimaryKeys = new ArrayList<>();
26 | mIntegerColumns = new ArrayList<>();
27 | mRealColumns = new ArrayList<>();
28 | mTextColumns = new ArrayList<>();
29 | }
30 |
31 | @NonNull
32 | public static TableBuilder create(@NonNull Table table) {
33 | TableBuilder builder = new TableBuilder();
34 | builder.mTableName = table.getTableName();
35 | return builder;
36 | }
37 |
38 | @NonNull
39 | public TableBuilder primaryKey(@NonNull String... keys) {
40 | mPrimaryKeys.clear();
41 | for (String key : keys) {
42 | if (!mPrimaryKeys.contains(key)) {
43 | mPrimaryKeys.add(key);
44 | }
45 | }
46 | return this;
47 | }
48 |
49 | @NonNull
50 | public TableBuilder intColumn(@NonNull String columnName) {
51 | if (!mIntegerColumns.contains(columnName)) {
52 | mIntegerColumns.add(columnName);
53 | }
54 | return this;
55 | }
56 |
57 | @NonNull
58 | public TableBuilder realColumn(@NonNull String columnName) {
59 | if (!mRealColumns.contains(columnName)) {
60 | mRealColumns.add(columnName);
61 | }
62 | return this;
63 | }
64 |
65 | @NonNull
66 | public TableBuilder textColumn(@NonNull String columnName) {
67 | if (!mTextColumns.contains(columnName)) {
68 | mTextColumns.add(columnName);
69 | }
70 | return this;
71 | }
72 |
73 | public void execute(@NonNull SQLiteDatabase database) {
74 | database.execSQL(buildSQL());
75 | }
76 |
77 | @VisibleForTesting
78 | String buildSQL() {
79 | if (mIntegerColumns.isEmpty() && mRealColumns.isEmpty() && mTextColumns.isEmpty()) {
80 | throw new IllegalStateException("Cannot create table with no columns");
81 | }
82 |
83 | StringBuilder builder = new StringBuilder();
84 | builder.append("CREATE TABLE IF NOT EXISTS ")
85 | .append(mTableName)
86 | .append("(");
87 |
88 | if (!mIntegerColumns.isEmpty()) {
89 | String column = mIntegerColumns.remove(0);
90 | builder.append(column)
91 | .append(" INTEGER");
92 | } else if (!mRealColumns.isEmpty()) {
93 | String column = mRealColumns.remove(0);
94 | builder.append(column)
95 | .append(" REAL");
96 | } else {
97 | String column = mTextColumns.remove(0);
98 | builder.append(column)
99 | .append(" TEXT");
100 | }
101 |
102 | for (String column : mIntegerColumns) {
103 | builder.append(", ")
104 | .append(column)
105 | .append(" INTEGER");
106 | }
107 |
108 | for (String column : mRealColumns) {
109 | builder.append(", ")
110 | .append(column)
111 | .append(" REAL");
112 | }
113 |
114 | for (String column : mTextColumns) {
115 | builder.append(", ")
116 | .append(column)
117 | .append(" TEXT");
118 | }
119 |
120 | if (!mPrimaryKeys.isEmpty()) {
121 | builder.append(", PRIMARY KEY (")
122 | .append(mPrimaryKeys.get(0));
123 | }
124 | for (int i = 1; i < mPrimaryKeys.size(); i++) {
125 | builder.append(", ").append(mPrimaryKeys.get(i));
126 | }
127 | if (!mPrimaryKeys.isEmpty()) {
128 | builder.append(")");
129 | }
130 | builder.append(");");
131 |
132 | return builder.toString();
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/test/java/ru/arturvasilov/sqlite/utils/TableBuilderTest.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.utils;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.junit.runners.JUnit4;
6 |
7 | import ru.arturvasilov.sqlite.testutils.JUnitTestTable;
8 |
9 | import static junit.framework.TestCase.assertEquals;
10 |
11 | @RunWith(JUnit4.class)
12 | public class TableBuilderTest {
13 |
14 | @Test(expected = IllegalStateException.class)
15 | public void testEmptyColumnsList() throws Exception {
16 | TableBuilder.create(JUnitTestTable.TABLE).buildSQL();
17 | }
18 |
19 | @Test
20 | public void testSingleIntegerColumn() throws Exception {
21 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(test INTEGER);";
22 |
23 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE)
24 | .intColumn("test")
25 | .buildSQL());
26 | }
27 |
28 | @Test
29 | public void testSingleRealColumn() throws Exception {
30 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(test REAL);";
31 |
32 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE)
33 | .realColumn("test")
34 | .buildSQL());
35 | }
36 |
37 | @Test
38 | public void testSingleStringColumn() throws Exception {
39 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(test TEXT);";
40 |
41 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE)
42 | .textColumn("test")
43 | .buildSQL());
44 | }
45 |
46 | @Test
47 | public void testPrimaryKeyWithIntColumn() throws Exception {
48 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(test INTEGER, PRIMARY KEY (test));";
49 |
50 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE)
51 | .intColumn("test")
52 | .primaryKey("test")
53 | .buildSQL());
54 | }
55 |
56 | @Test
57 | public void testManyColumnsWithoutPrimaryKey() throws Exception {
58 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(int1 INTEGER, int2 INTEGER, " +
59 | "real1 REAL, string1 TEXT, string2 TEXT);";
60 |
61 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE)
62 | .intColumn("int1")
63 | .textColumn("string1")
64 | .intColumn("int2")
65 | .realColumn("real1")
66 | .textColumn("string2")
67 | .buildSQL());
68 | }
69 |
70 | @Test
71 | public void testManyColumnsWithPrimaryKey() throws Exception {
72 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(int1 INTEGER, int2 INTEGER," +
73 | " real1 REAL, string1 TEXT, string2 TEXT, PRIMARY KEY (int1));";
74 |
75 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE)
76 | .intColumn("int1")
77 | .textColumn("string1")
78 | .intColumn("int2")
79 | .realColumn("real1")
80 | .textColumn("string2")
81 | .primaryKey("int1")
82 | .buildSQL());
83 | }
84 |
85 | @Test
86 | public void testManyColumnsWithMultiplePrimaryKey() throws Exception {
87 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(int1 INTEGER, int2 INTEGER, " +
88 | "real1 REAL, real2 REAL, string1 TEXT, string2 TEXT, PRIMARY KEY (int1, real1, string2));";
89 |
90 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE)
91 | .intColumn("int1")
92 | .realColumn("real1")
93 | .textColumn("string1")
94 | .intColumn("int2")
95 | .textColumn("string2")
96 | .realColumn("real2")
97 | .primaryKey("int1", "real1", "string2")
98 | .buildSQL());
99 | }
100 |
101 | @Test
102 | public void testSameColumnsIgnored() throws Exception {
103 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(testInt INTEGER, testReal REAL, testText TEXT);";
104 |
105 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE)
106 | .intColumn("testInt")
107 | .intColumn("testInt")
108 | .realColumn("testReal")
109 | .realColumn("testReal")
110 | .textColumn("testText")
111 | .textColumn("testText")
112 | .buildSQL());
113 | }
114 |
115 | @Test
116 | public void testSamePrimaryKeysIgnored() throws Exception {
117 | String sql = "CREATE TABLE IF NOT EXISTS JUnitTestTable(test INTEGER, PRIMARY KEY (test));";
118 |
119 | assertEquals(sql, TableBuilder.create(JUnitTestTable.TABLE)
120 | .intColumn("test")
121 | .primaryKey("test", "test")
122 | .buildSQL());
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/arturvasilov/sqlite/core/SQLiteContentProvider.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.core;
2 |
3 | import android.content.ContentProvider;
4 | import android.content.ContentUris;
5 | import android.content.ContentValues;
6 | import android.database.Cursor;
7 | import android.net.Uri;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 | import android.text.TextUtils;
11 |
12 | import org.sqlite.database.sqlite.SQLiteDatabase;
13 |
14 | /**
15 | * This class provides implementation for all operations in ContentProvider
16 | * and based on SQLite database.
17 | *
18 | * You only have to implement two methods {@link SQLiteContentProvider#prepareConfig(SQLiteConfig)}
19 | * and {@link SQLiteContentProvider#prepareSchema(SQLiteSchema)}, the rest is handled by the library.
20 | *
21 | * @author Artur Vasilov
22 | */
23 | public abstract class SQLiteContentProvider extends ContentProvider {
24 |
25 | private SQLiteSchema mSchema;
26 |
27 | private SQLiteHelper mSQLiteHelper;
28 |
29 | private static String sContentAuthority;
30 | private static Uri sBaseUri;
31 |
32 | /**
33 | * In this method you can specify configuration for your database (for this moment only name and authority)
34 | *
35 | * @param config - configuration for SQLite database
36 | */
37 | protected abstract void prepareConfig(@NonNull SQLiteConfig config);
38 |
39 | /**
40 | * In this method you must add all tables you want to use in your app.
41 | * To add table call {@link SQLiteSchema#register(Table)}
42 | *
43 | * @param schema - schema for SQLite database
44 | */
45 | protected abstract void prepareSchema(@NonNull SQLiteSchema schema);
46 |
47 | @Override
48 | public final boolean onCreate() {
49 | SQLiteConfig config = new SQLiteConfig(getContext());
50 | prepareConfig(config);
51 |
52 | sContentAuthority = config.getAuthority();
53 | sBaseUri = Uri.parse("content://" + sContentAuthority);
54 |
55 | mSchema = new SQLiteSchema();
56 | prepareSchema(mSchema);
57 |
58 | mSQLiteHelper = new SQLiteHelper(getContext(), config, mSchema);
59 | return true;
60 | }
61 |
62 | @Nullable
63 | @Override
64 | public final String getType(@NonNull Uri uri) {
65 | return mSchema.findTable(uri);
66 | }
67 |
68 | @Nullable
69 | @Override
70 | public final Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
71 | SQLiteDatabase database = mSQLiteHelper.getWritableDatabase();
72 | String table = getType(uri);
73 | if (TextUtils.isEmpty(table)) {
74 | throw new IllegalArgumentException("No such table to query");
75 | } else {
76 | return database.query(table,
77 | projection,
78 | selection,
79 | selectionArgs,
80 | null,
81 | null,
82 | sortOrder);
83 | }
84 | }
85 |
86 | @NonNull
87 | @Override
88 | public final Uri insert(@NonNull Uri uri, ContentValues values) {
89 | SQLiteDatabase database = mSQLiteHelper.getWritableDatabase();
90 | String table = getType(uri);
91 | if (TextUtils.isEmpty(table)) {
92 | throw new IllegalArgumentException("No such table to insert");
93 | } else {
94 | long id = database.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
95 | return ContentUris.withAppendedId(uri, id);
96 | }
97 | }
98 |
99 | @Override
100 | public final int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
101 | SQLiteDatabase database = mSQLiteHelper.getWritableDatabase();
102 | String table = getType(uri);
103 | if (TextUtils.isEmpty(table)) {
104 | throw new IllegalArgumentException("No such table to insert");
105 | } else {
106 | int numInserted = 0;
107 | database.beginTransaction();
108 | try {
109 | for (ContentValues contentValues : values) {
110 | long id = database.insertWithOnConflict(table, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE);
111 | if (id > 0) {
112 | numInserted++;
113 | }
114 | }
115 | database.setTransactionSuccessful();
116 | } finally {
117 | database.endTransaction();
118 | }
119 | return numInserted;
120 | }
121 | }
122 |
123 | @Override
124 | public final int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
125 | SQLiteDatabase database = mSQLiteHelper.getWritableDatabase();
126 | String table = getType(uri);
127 | if (TextUtils.isEmpty(table)) {
128 | throw new IllegalArgumentException("No such table to delete");
129 | } else {
130 | return database.delete(table, selection, selectionArgs);
131 | }
132 | }
133 |
134 | @Override
135 | public final int update(@NonNull Uri uri, ContentValues values,
136 | String selection, String[] selectionArgs) {
137 | SQLiteDatabase database = mSQLiteHelper.getWritableDatabase();
138 | String table = getType(uri);
139 | if (TextUtils.isEmpty(table)) {
140 | throw new IllegalArgumentException("No such table to update");
141 | } else {
142 | return database.update(table, values, selection, selectionArgs);
143 | }
144 | }
145 |
146 | @NonNull
147 | static String getContentAuthority() {
148 | return sContentAuthority;
149 | }
150 |
151 | @NonNull
152 | static Uri getBaseUri() {
153 | return sBaseUri;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/arturvasilov/sqlite/core/Where.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.core;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Collections;
8 | import java.util.List;
9 |
10 | /**
11 | * Class for constructing SQL queries using Java methods.
12 | * All most popular operators (=, <>, >, <, >=, <=, LIKE, BETWEEN, IN, OR, AND) are supported.
13 | *
14 | * If there is no method that you need, you can always construct query manually with {@link Where#where(String, Object...)}.
15 | *
16 | * Class also supports LIMIT and OFFSET with methods {@link Where#limit(int)} and {@link Where#offset(int)} respectively.
17 | *
18 | * Typical usage of this class looks like this:
19 | *
20 | * {code
21 | * Where where = Where.create()
22 | * .beginGroup()
23 | * .greaterThanOrEqualTo("age", 18)
24 | * .or()
25 | * .lessThan("age", 50)
26 | * .endGroup()
27 | * .and()
28 | * .like("name", "Sm")
29 | * .limit(1);
30 | * }
31 | *
32 | *
33 | * @author Artur Vasilov
34 | */
35 | public class Where {
36 |
37 | private final StringBuilder mWhereBuilder;
38 |
39 | private final List mBindValues;
40 |
41 | private String mLimit;
42 | private String mOffset;
43 |
44 | private Where() {
45 | mWhereBuilder = new StringBuilder();
46 | mBindValues = new ArrayList<>();
47 | }
48 |
49 | @NonNull
50 | public static Where create() {
51 | return new Where();
52 | }
53 |
54 | @NonNull
55 | public Where where(@NonNull String where, Object... values) {
56 | mWhereBuilder.append(where);
57 | Collections.addAll(mBindValues, values);
58 | return this;
59 | }
60 |
61 | @NonNull
62 | public Where equalTo(@NonNull String column, @NonNull Object value) {
63 | return where(column, " = ?", value);
64 | }
65 |
66 | @NonNull
67 | public Where notEqualTo(@NonNull String column, @NonNull Object value) {
68 | return where(column, " <> ?", value);
69 | }
70 |
71 | @NonNull
72 | public Where lessThan(@NonNull String column, @NonNull Object value) {
73 | return where(column, " < ?", value);
74 | }
75 |
76 | @NonNull
77 | public Where lessThanOrEqualTo(@NonNull String column, @NonNull Object value) {
78 | return where(column, " <= ?", value);
79 | }
80 |
81 | @NonNull
82 | public Where greaterThan(@NonNull String column, @NonNull Object value) {
83 | return where(column, " > ?", value);
84 | }
85 |
86 | @NonNull
87 | public Where greaterThanOrEqualTo(@NonNull String column, @NonNull Object value) {
88 | return where(column, " >= ?", value);
89 | }
90 |
91 | @NonNull
92 | public Where like(@NonNull String column, @NonNull Object value) {
93 | return where(column, " LIKE ?", value);
94 | }
95 |
96 | @NonNull
97 | public Where between(@NonNull String column, @NonNull Object first, @NonNull Object second) {
98 | return where(column, " BETWEEN ? AND ?", first, second);
99 | }
100 |
101 | @NonNull
102 | public Where isNull(@NonNull String column) {
103 | return where(column, " IS NULL");
104 | }
105 |
106 | @NonNull
107 | public Where notNull(@NonNull String column) {
108 | return where(column, " NOT NULL");
109 | }
110 |
111 | @NonNull
112 | public Where in(@NonNull String column, @NonNull Object... values) {
113 | final int last = values.length - 1;
114 | mWhereBuilder.append(column).append(" IN(");
115 | for (int i = 0; i < values.length; ++i) {
116 | mWhereBuilder.append("?");
117 | if (i < last) {
118 | mWhereBuilder.append(", ");
119 | }
120 | }
121 | mWhereBuilder.append(")");
122 | Collections.addAll(mBindValues, values);
123 | return this;
124 | }
125 |
126 | @NonNull
127 | public Where beginGroup() {
128 | mWhereBuilder.append("(");
129 | return this;
130 | }
131 |
132 | @NonNull
133 | public Where endGroup() {
134 | mWhereBuilder.append(")");
135 | return this;
136 | }
137 |
138 | @NonNull
139 | public Where and() {
140 | mWhereBuilder.append(" AND ");
141 | return this;
142 | }
143 |
144 | @NonNull
145 | public Where or() {
146 | mWhereBuilder.append(" OR ");
147 | return this;
148 | }
149 |
150 | @NonNull
151 | public Where limit(int limit) {
152 | mLimit = String.valueOf(limit);
153 | return this;
154 | }
155 |
156 | @NonNull
157 | public Where offset(int offset) {
158 | mOffset = String.valueOf(offset);
159 | return this;
160 | }
161 |
162 | @Nullable
163 | public String where() {
164 | if (mWhereBuilder.length() == 0) {
165 | return null;
166 | }
167 | return mWhereBuilder.toString();
168 | }
169 |
170 | @Nullable
171 | public String[] whereArgs() {
172 | if (mBindValues.isEmpty()) {
173 | return null;
174 | }
175 | String[] args = new String[mBindValues.size()];
176 | for (int i = 0; i < mBindValues.size(); i++) {
177 | args[i] = String.valueOf(mBindValues.get(i));
178 | }
179 | return args;
180 | }
181 |
182 | @Nullable
183 | public String limit() {
184 | String result = "";
185 | if (mLimit != null && !mLimit.isEmpty()) {
186 | result += " LIMIT " + mLimit;
187 | }
188 | if (mOffset != null && !mOffset.isEmpty()) {
189 | result += " OFFSET " + mOffset;
190 | }
191 | if (result.isEmpty()) {
192 | return null;
193 | }
194 | return result;
195 | }
196 |
197 | @NonNull
198 | private Where where(@NonNull String column, @NonNull String operand, @NonNull Object... values) {
199 | mWhereBuilder.append(column).append(operand);
200 | Collections.addAll(mBindValues, values);
201 | return this;
202 | }
203 |
204 | }
--------------------------------------------------------------------------------
/app/src/test/java/ru/arturvasilov/sqlite/core/WhereTest.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.core;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | import junit.framework.Assert;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.junit.runners.JUnit4;
11 |
12 | import static junit.framework.Assert.assertNotNull;
13 | import static org.junit.Assert.assertEquals;
14 | import static org.junit.Assert.assertNull;
15 |
16 | /**
17 | * @author Artur Vasilov
18 | */
19 | @RunWith(JUnit4.class)
20 | public class WhereTest {
21 |
22 | @Test
23 | public void testNoParameters() throws Exception {
24 | checkWhere(null, null, null, Where.create());
25 | }
26 |
27 | @Test
28 | public void testEqualTo() throws Exception {
29 | Where where = Where.create().equalTo("id", 5);
30 | checkWhere("id = ?", new String[]{"5"}, null, where);
31 | }
32 |
33 | @Test
34 | public void testNotEqualTo() throws Exception {
35 | Where where = Where.create().notEqualTo("text", "abcd");
36 | checkWhere("text <> ?", new String[]{"abcd"}, null, where);
37 | }
38 |
39 | @Test
40 | public void testLessThan() throws Exception {
41 | Where where = Where.create().lessThan("age", 18);
42 | checkWhere("age < ?", new String[]{"18"}, null, where);
43 | }
44 |
45 | @Test
46 | public void testLessThanOrEqualTo() throws Exception {
47 | Where where = Where.create().lessThanOrEqualTo("price", 207.18);
48 | checkWhere("price <= ?", new String[]{"207.18"}, null, where);
49 | }
50 |
51 | @Test
52 | public void testGreaterThan() throws Exception {
53 | Where where = Where.create().greaterThan("year", 2008);
54 | checkWhere("year > ?", new String[]{"2008"}, null, where);
55 | }
56 |
57 | @Test
58 | public void testGreaterThanOrEqualTo() throws Exception {
59 | Where where = Where.create().greaterThanOrEqualTo("age", 18);
60 | checkWhere("age >= ?", new String[]{"18"}, null, where);
61 | }
62 |
63 | @Test
64 | public void testLike() throws Exception {
65 | Where where = Where.create().like("title", "Politics");
66 | checkWhere("title LIKE ?", new String[]{"Politics"}, null, where);
67 | }
68 |
69 | @Test
70 | public void testBetween() throws Exception {
71 | Where where = Where.create().between("price", 17.5, 19.8);
72 | checkWhere("price BETWEEN ? AND ?", new String[]{"17.5", "19.8"}, null, where);
73 | }
74 |
75 | @Test
76 | public void testIsNull() throws Exception {
77 | Where where = Where.create().isNull("body");
78 | checkWhere("body IS NULL", null, null, where);
79 | }
80 |
81 | @Test
82 | public void testNotNull() throws Exception {
83 | Where where = Where.create().notNull("body");
84 | checkWhere("body NOT NULL", null, null, where);
85 | }
86 |
87 | @Test
88 | public void testIn() throws Exception {
89 | Where where = Where.create().in("id", 5, 6, 7, 9);
90 | checkWhere("id IN(?, ?, ?, ?)", new String[]{"5", "6", "7", "9"}, null, where);
91 | }
92 |
93 | @Test
94 | public void testAnd() throws Exception {
95 | Where where = Where.create().greaterThanOrEqualTo("age", 18).and().lessThan("age", 45);
96 | checkWhere("age >= ? AND age < ?", new String[]{"18", "45"}, null, where);
97 | }
98 |
99 | @Test
100 | public void testOr() throws Exception {
101 | Where where = Where.create().equalTo("city", "London").or().like("country", "R");
102 | checkWhere("city = ? OR country LIKE ?", new String[]{"London", "R"}, null, where);
103 | }
104 |
105 | @Test
106 | public void testLimit() throws Exception {
107 | Where where = Where.create().limit(3);
108 | checkWhere(null, null, " LIMIT 3", where);
109 | }
110 |
111 | @Test
112 | public void testOffset() throws Exception {
113 | Where where = Where.create().offset(7);
114 | checkWhere(null, null, " OFFSET 7", where);
115 | }
116 |
117 | @Test
118 | public void testLimitAndOffset() throws Exception {
119 | Where where = Where.create().limit(1).offset(18);
120 | checkWhere(null, null, " LIMIT 1 OFFSET 18", where);
121 | }
122 |
123 | @Test
124 | public void testWhere() throws Exception {
125 | Where where = Where.create().where("id > ? AND age <= ?", 1000, 60);
126 | checkWhere("id > ? AND age <= ?", new String[]{"1000", "60"}, null, where);
127 | }
128 |
129 | @Test
130 | public void testGroups() throws Exception {
131 | Where where = Where.create()
132 | .beginGroup()
133 | .greaterThanOrEqualTo("age", 18)
134 | .or()
135 | .lessThan("age", 50)
136 | .endGroup()
137 | .and()
138 | .like("name", "Sm");
139 | checkWhere("(age >= ? OR age < ?) AND name LIKE ?", new String[]{"18", "50", "Sm"}, null, where);
140 | }
141 |
142 | @Test
143 | public void testComplexQuery() throws Exception {
144 | Where where = Where.create()
145 | .in("id", 18, 20, 24)
146 | .and()
147 | .like("text", "hello")
148 | .limit(1);
149 |
150 | checkWhere("id IN(?, ?, ?) AND text LIKE ?", new String[]{"18", "20", "24", "hello"}, " LIMIT 1", where);
151 | }
152 |
153 | private void checkWhere(@Nullable String where, @Nullable String[] args,
154 | @Nullable String limit, @NonNull Where testWhere) {
155 | assertEquals(where, testWhere.where());
156 | if (args == null) {
157 | assertNull(testWhere.whereArgs());
158 | } else {
159 | String[] whereArgs = testWhere.whereArgs();
160 | assertNotNull(whereArgs);
161 | assertEquals(args.length, whereArgs.length);
162 | for (int i = 0; i < args.length; i++) {
163 | assertEquals(args[i], whereArgs[i]);
164 | }
165 | }
166 | Assert.assertEquals(limit, testWhere.limit());
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/app/src/main/java/ru/arturvasilov/sqlite/rx/RxSQLite.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.rx;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 | import android.support.annotation.NonNull;
6 |
7 | import java.util.List;
8 | import java.util.concurrent.Callable;
9 |
10 | import io.reactivex.Observable;
11 | import io.reactivex.ObservableSource;
12 | import io.reactivex.disposables.Disposable;
13 | import io.reactivex.functions.Function;
14 | import ru.arturvasilov.sqlite.core.SQLite;
15 | import ru.arturvasilov.sqlite.core.Table;
16 | import ru.arturvasilov.sqlite.core.Where;
17 |
18 | /**
19 | * For the documentation please take a look at {@link SQLite},
20 | * Most of the methods in this class simply wraps {@link SQLite} methods in Observables.
21 | *
22 | * Note: this class doesn't handled doing operations in the background - it's up to the user
23 | * (since in most cases you won't simply subscribe for data, but transform it, so you'll have to write
24 | * subscribeOn and observeOn one more time.
25 | *
26 | * @author Artur Vasilov
27 | */
28 | public class RxSQLite {
29 |
30 | private static RxSQLite sSQLite;
31 |
32 | /**
33 | * Before calling this method be sure that you've successfully initialized SQLite instance
34 | * (by {@link SQLite#initialize(Context)}), since all of the methods in this class are simple wrappers.
35 | *
36 | * @return singleton instance of RxSQLite
37 | */
38 | @NonNull
39 | public static RxSQLite get() {
40 | SQLite.get();
41 |
42 | RxSQLite sqLite = sSQLite;
43 | if (sqLite == null) {
44 | synchronized (SQLite.class) {
45 | sqLite = sSQLite;
46 | if (sqLite == null) {
47 | sqLite = sSQLite = new RxSQLite();
48 | }
49 | }
50 | }
51 | return sqLite;
52 | }
53 |
54 | /**
55 | * {@link SQLite#query(Table)}
56 | */
57 | @NonNull
58 | public Observable> query(@NonNull final Table table) {
59 | return query(table, Where.create());
60 | }
61 |
62 | /**
63 | * {@link SQLite#query(Table, Where)}
64 | */
65 | @NonNull
66 | public Observable> query(@NonNull final Table table, @NonNull final Where where) {
67 | return Observable.fromCallable(new Callable>() {
68 | @Override
69 | public List call() throws Exception {
70 | return SQLite.get().query(table, where);
71 | }
72 | });
73 | }
74 |
75 | /**
76 | * {@link SQLite#querySingle(Table)}
77 | */
78 | @NonNull
79 | public Observable querySingle(@NonNull final Table table) {
80 | return querySingle(table, Where.create());
81 | }
82 |
83 | /**
84 | * {@link SQLite#querySingle(Table, Where)}
85 | *
86 | * Observable is guarantee to contain no more than one element.
87 | * If you want to get observable of elements, simply call {@link RxSQLite#query(Table, Where)}
88 | * all apply {@link Observable#flatMap(Function)} with {@link Observable#fromIterable(Iterable)} to it.
89 | */
90 | @NonNull
91 | public Observable querySingle(@NonNull final Table table, @NonNull final Where where) {
92 | return Observable.fromCallable(new Callable() {
93 | @Override
94 | public T call() throws Exception {
95 | return SQLite.get().querySingle(table, where);
96 | }
97 | }).flatMap(new Function>() {
98 | @Override
99 | public ObservableSource apply(T t) throws Exception {
100 | return t == null ? Observable.empty() : Observable.just(t);
101 | }
102 | }).take(1);
103 | }
104 |
105 | /**
106 | * {@link SQLite#insert(Table, Object)}
107 | */
108 | @NonNull
109 | public Observable insert(@NonNull final Table table, @NonNull final T object) {
110 | return Observable.fromCallable(new Callable() {
111 | @Override
112 | public Uri call() throws Exception {
113 | return SQLite.get().insert(table, object);
114 | }
115 | });
116 | }
117 |
118 | /**
119 | * {@link SQLite#insert(Table, List)}
120 | */
121 | @NonNull
122 | public Observable insert(@NonNull final Table table, @NonNull final List objects) {
123 | return Observable.fromCallable(new Callable() {
124 | @Override
125 | public Integer call() throws Exception {
126 | return SQLite.get().insert(table, objects);
127 | }
128 | });
129 | }
130 |
131 | /**
132 | * {@link SQLite#delete(Table)}
133 | */
134 | @NonNull
135 | public Observable delete(@NonNull final Table table) {
136 | return delete(table, Where.create());
137 | }
138 |
139 | /**
140 | * {@link SQLite#delete(Table, Where)}
141 | */
142 | @NonNull
143 | public Observable delete(@NonNull final Table table, @NonNull final Where where) {
144 | return Observable.fromCallable(new Callable() {
145 | @Override
146 | public Integer call() throws Exception {
147 | return SQLite.get().delete(table, where);
148 | }
149 | });
150 | }
151 |
152 | /**
153 | * {@link SQLite#update(Table, Where, Object)}
154 | */
155 | @NonNull
156 | public Observable update(@NonNull final Table table, @NonNull final Where where,
157 | @NonNull final T newObject) {
158 | return Observable.fromCallable(new Callable() {
159 | @Override
160 | public Integer call() throws Exception {
161 | return SQLite.get().update(table, where, newObject);
162 | }
163 | });
164 | }
165 |
166 | /**
167 | * Returns observable that emits new items when passed table is changed.
168 | * For more information please take a look at {@link TableObservable}
169 | *
170 | * This observable never completes and there is no methods in RxSQLite to dispose,
171 | * so you have to control disposables manually like this:
172 | *
173 | *
174 | * {code
175 | * private Disposable mPersonsDisposable;
176 | *
177 | * //...
178 | *
179 | * @Override
180 | * protected void onResume() {
181 | * super.onResume();
182 | * mPersonsDisposable = RxSQLite.get().observeChanges(PersonTable.TABLE)
183 | * .subscribe(value -> {
184 | * // table changed
185 | * });
186 | * }
187 | *
188 | * @Override
189 | * protected void onPause() {
190 | * super.onPause();
191 | * mPersonsDisposable.dispose();
192 | * }
193 | * }
194 | *
195 | * {@link Disposable#dispose()} will also detach ContentProvider observer
196 | */
197 | @NonNull
198 | public TableObservable observeChanges(@NonNull final Table table) {
199 | return new TableObservable<>(table);
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SQLite [](https://github.com/ArturVasilov/SQLite/blob/master/LICENSE) [](https://github.com/ArturVasilov/SQLite) [](https://coveralls.io/github/ArturVasilov/SQLite)
2 |
3 | #### Yet another Android library for database
4 |
5 | Database library for Android based on SQLite and ContentProvider, which provides simple way for all operations with data.
6 |
7 | ### Advantages:
8 |
9 | * 0 reflection
10 | * Full customization (since library is a simple wrapper on ContentProvider you can always have direct access to it)
11 | * Flexible interface for manipulating data
12 | * Data migration
13 | * RxJava support
14 |
15 | ### Gradle
16 |
17 | ```groovy
18 | compile 'ru.arturvasilov:sqlite:0.2.0'
19 | ```
20 |
21 | ### Tables:
22 |
23 | Instead of generating code and using your model classes for database directly, this library uses tables classes for each table in database (or in fact for each class you want to store). It's routine to write these classes but it also give you more control, which is useful for features like data migration.
24 |
25 | So for each table in database you have to create a class which extends ```Table``` interface or ```BaseTable``` class like this:
26 |
27 | ```java
28 | public class PersonTable extends BaseTable {
29 |
30 | public static final Table TABLE = new PersonTable();
31 |
32 | public static final String ID = "id";
33 | public static final String NAME = "name";
34 | public static final String AGE = "age";
35 |
36 | @Override
37 | public void onCreate(@NonNull SQLiteDatabase database) {
38 | TableBuilder.create(this)
39 | .intColumn(ID)
40 | .textColumn(NAME)
41 | .intColumn(AGE)
42 | .primaryKey(ID)
43 | .execute(database);
44 | }
45 |
46 | @NonNull
47 | @Override
48 | public ContentValues toValues(@NonNull Person person) {
49 | ContentValues values = new ContentValues();
50 | values.put(ID, person.getId());
51 | values.put(NAME, person.getName());
52 | values.put(AGE, person.getAge());
53 | return values;
54 | }
55 |
56 | @NonNull
57 | @Override
58 | public Person fromCursor(@NonNull Cursor cursor) {
59 | int id = cursor.getInt(cursor.getColumnIndex(ID));
60 | String name = cursor.getString(cursor.getColumnIndex(NAME));
61 | int age = cursor.getInt(cursor.getColumnIndex(AGE));
62 | return new Person(id, name, age);
63 | }
64 | }
65 | ```
66 |
67 | ### Setting Up
68 |
69 | After creating tables your need to specify ContentProvider, where you add these tables:
70 | ```java
71 | public class SQLiteProvider extends SQLiteContentProvider {
72 |
73 | private static final String DATABASE_NAME = "mydatabase.db";
74 | private static final String CONTENT_AUTHORITY = "com.myapp";
75 |
76 | @Override
77 | protected void prepareConfig(@NonNull SQLiteConfig config) {
78 | config.setDatabaseName(DATABASE_NAME);
79 | config.setAuthority(CONTENT_AUTHORITY);
80 | }
81 |
82 | @Override
83 | protected void prepareSchema(@NonNull Schema schema) {
84 | schema.register(PersonTable.TABLE);
85 | }
86 | }
87 | ```
88 |
89 | And register it in the AndroidManifest.xml:
90 | ```xml
91 |
95 | ```
96 |
97 | And initialize SQLite:
98 | ```java
99 | public class MyApplication extends Application {
100 |
101 | @Override
102 | public void onCreate() {
103 | super.onCreate();
104 | SQLite.initialize(this);
105 | }
106 | }
107 | ```
108 |
109 | ### Supported data types
110 |
111 | 1. _int_, _short_ and _long_ are supported with intColumn method
112 | 2. _boolean_ is also supported with ```intColumn``` method, but you still have to manually convert it in ```Table#toValues``` and ```Table#fromCursor``` methods
113 | 3. _double_ and _float_ are supported with ```realColumn```.
114 | 4. String, enums and custom objects should be saved as TEXT with ```textColumn``` method. For custom objects you may consider json serialization (relations is not in the nearest plan).
115 |
116 | ### Operations
117 |
118 | All operations should go through the SQLite or RxSQLite classes. You can access to ContentProvider directly, but note that you can loose features like observing changes in tables.
119 |
120 | Every operation (query, insert, update, delete) exist both in direct and rx ways.
121 |
122 | You can query for data like so:
123 | ```java
124 | Person person = SQLite.get().queryObject(PersonTable.TABLE);
125 | // or for list
126 | List persons = SQLite.get().query(PersonTable.TABLE);
127 | // or with where
128 | List adults = SQLite.get().query(PersonTable.TABLE, Where.create().greaterThanOrEqualTo(PersonTable.AGE, 18));
129 | ```
130 |
131 | Similar way for RxSQLite:
132 |
133 | ```java
134 | RxSQLite.get().query(PersonTable.TABLE)
135 | .subscribe(persons -> {
136 | //do something with persons
137 | });
138 | ```
139 |
140 | *Note*: RxSQLite doesn't take care about doing operations in background - it's up to your.
141 |
142 | And it's all the same for other operations.
143 |
144 | ### Observing changes
145 |
146 | Observing changes in database is a great way for communication between your UI classes and network layer. This library provides flexible implementation of this pattern.
147 |
148 | Note that starting from version 0.1.3 automatic notifications are disabled by default.
149 | You can either update call ```SQLite.get().notifyTableChanged(Table)``` manually or enable automatic notifications
150 | (notifications will be send for each single operation) with ```SQLite.get().enableAutomaticNotifications()```.
151 |
152 | Notifications about changes in table are called in the main thread.
153 |
154 | Get notified when table changed:
155 | ```java
156 | public class MainActivity extends AppCompatActivity implements BasicTableObserver {
157 |
158 | // ...
159 |
160 | @Override
161 | protected void onResume() {
162 | super.onResume();
163 | SQLite.get().registerObserver(PersonTable.TABLE, this);
164 | }
165 |
166 | @Override
167 | protected void onPause() {
168 | super.onPause();
169 | SQLite.get().unregisterObserver(this);
170 | }
171 |
172 | @Override
173 | public void onTableChanged() {
174 | // You now know that content in the table has changed
175 | }
176 |
177 | }
178 | ```
179 |
180 | If you also want to get all the content from database (typical for the observing changes), you can change *BasicTableObserver* to *ContentTableObserver* and implement it's method:
181 | ```java
182 | @Override
183 | public void onTableChanged(@NonNull List persons) {
184 | // handle changed persons
185 | }
186 | ```
187 | Everything else is the same! And more, you don't need to care about performance, for these changes library reads queries tables in the background already. *Note*: that's why you should be careful using this type of subscribption - frequent changes in table may affect your app.
188 |
189 | It's even more flexible with RxSQLite:
190 | ```java
191 | private Disposable mPersonsDisposable;
192 |
193 | //...
194 |
195 | @Override
196 | protected void onResume() {
197 | super.onResume();
198 | mPersonsDisposable = RxSQLite.get().observeChanges(PersonTable.TABLE)
199 | .subscribe(value -> {
200 | // table changed
201 | });
202 | }
203 |
204 | @Override
205 | protected void onPause() {
206 | super.onPause();
207 | mPersonsDisposable.dispose();
208 | }
209 | ```
210 |
211 | You can also query all the table with one simple call:
212 | ```java
213 | mPersonsDisposable = RxSQLite.get().observeChanges(PersonTable.TABLE).withQuery().subscribe(persons -> {});
214 | ```
215 |
216 | *Note* you still have to manage subscription manually.
217 |
218 | ### Data migration
219 |
220 | Data migration is always is most painful part. Library provides you a way to update the table and decide how it should be updated.
221 |
222 | Each table has method ```getLastUpgradeVersion```, which by default returns 1. Current database version is the maximum of all tables versions.
223 |
224 | If you changed any table, simple update it's version. All others table won't be affected, if their version is less than maximum:
225 | ```java
226 | @Override
227 | public int getLastUpgradeVersion() {
228 | return 2;
229 | }
230 | ```
231 |
232 | By default, *onUpdate* method simply recreates the table, but you can customize it by overriding this method:
233 | ```java
234 | @Override
235 | public void onUpgrade(@NonNull SQLiteDatabase database) {
236 | database.execSQL("DROP TABLE IF EXISTS " + getTableName());
237 | onCreate(database);
238 | }
239 | ```
240 |
241 | ### Samples:
242 |
243 | [StackDroid](https://github.com/ArturVasilov/StackDroid) application uses this library to work with database.
244 |
245 | ### Future plans
246 |
247 | 1. Support temporary entries in database
248 | 2. Generate most of boilerplate code
249 | 3. Support relations
250 | 4. Add triggers and functions
251 |
252 | ### Issues
253 |
254 | Feel free to create issues or even pull requests!
--------------------------------------------------------------------------------
/app/src/androidTest/java/ru/arturvasilov/sqlite/rx/RxSQLiteTest.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.rx;
2 |
3 | import android.support.test.InstrumentationRegistry;
4 | import android.support.test.runner.AndroidJUnit4;
5 |
6 | import org.junit.After;
7 | import org.junit.Assert;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.mockito.ArgumentCaptor;
12 | import org.mockito.Mockito;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | import io.reactivex.Observable;
18 | import io.reactivex.disposables.Disposable;
19 | import io.reactivex.functions.BiFunction;
20 | import io.reactivex.functions.Consumer;
21 | import ru.arturvasilov.sqlite.core.SQLite;
22 | import ru.arturvasilov.sqlite.core.Where;
23 | import ru.arturvasilov.sqlite.testutils.RxUtils;
24 | import ru.arturvasilov.sqlite.testutils.TestObject;
25 | import ru.arturvasilov.sqlite.testutils.TestTable;
26 |
27 | import static org.junit.Assert.assertEquals;
28 | import static org.junit.Assert.assertNotNull;
29 | import static org.junit.Assert.fail;
30 | import static org.mockito.Matchers.anyBoolean;
31 | import static org.mockito.Matchers.anyListOf;
32 |
33 | /**
34 | * @author Artur Vasilov
35 | */
36 | @RunWith(AndroidJUnit4.class)
37 | public class RxSQLiteTest {
38 |
39 | @Before
40 | public void setUp() throws Exception {
41 | SQLite.initialize(InstrumentationRegistry.getContext());
42 | RxUtils.setupTestSchedulers();
43 | SQLite.get().disableAutomaticNotifications();
44 | }
45 |
46 | @Test
47 | public void testSingleElement() throws Exception {
48 | TestObject testElement = new TestObject(10, 10, "abc");
49 | SQLite.get().insert(TestTable.TABLE, testElement);
50 |
51 | TestObject savedElement = RxSQLite.get()
52 | .querySingle(TestTable.TABLE)
53 | .test()
54 | .values()
55 | .get(0);
56 |
57 | Assert.assertEquals(testElement, savedElement);
58 | }
59 |
60 | @Test
61 | public void testElementsList() throws Exception {
62 | List elements = new ArrayList<>();
63 | elements.add(new TestObject(1, 9.5, "a"));
64 | elements.add(new TestObject(2, 6.7, "ab"));
65 | elements.add(new TestObject(3, 8.2, "abc"));
66 | elements.add(new TestObject(4, 3.4, "abcd"));
67 | elements.add(new TestObject(5, 6.5, "abcde"));
68 | SQLite.get().insert(TestTable.TABLE, elements);
69 |
70 | Observable.zip(RxSQLite.get().query(TestTable.TABLE),
71 | Observable.just(elements), new BiFunction, List, Object>() {
72 | @Override
73 | public Object apply(List testElements, List savedElements) throws Exception {
74 | assertEquals(testElements.size(), savedElements.size());
75 | for (int i = 0; i < testElements.size(); i++) {
76 | assertEquals(testElements.size(), savedElements.size());
77 | }
78 | return null;
79 | }
80 | })
81 | .test();
82 | }
83 |
84 | @Test
85 | public void testQueryWithParameters() throws Exception {
86 | List elements = new ArrayList<>();
87 | elements.add(new TestObject(1, 9.5, "a"));
88 | elements.add(new TestObject(2, 6.7, "ab"));
89 | elements.add(new TestObject(3, 8.2, "abc"));
90 | SQLite.get().insert(TestTable.TABLE, elements);
91 |
92 | int count = RxSQLite.get().query(TestTable.TABLE, Where.create().lessThanOrEqualTo(TestTable.ID, 2))
93 | .test()
94 | .values()
95 | .get(0)
96 | .size();
97 |
98 | assertEquals(2, count);
99 | }
100 |
101 | @Test
102 | public void testEmptyTable() throws Exception {
103 | RxSQLite.get().querySingle(TestTable.TABLE)
104 | .subscribe(new Consumer() {
105 | @Override
106 | public void accept(TestObject testObject) {
107 | fail();
108 | }
109 | }, new Consumer() {
110 | @Override
111 | public void accept(Throwable throwable) {
112 | fail();
113 | }
114 | });
115 | }
116 |
117 | @Test
118 | public void testInsertElement() throws Exception {
119 | assertNotNull(RxSQLite.get().insert(TestTable.TABLE, new TestObject(100, 7, "100")).test().values().get(0));
120 | }
121 |
122 | @Test
123 | public void testInsertList() throws Exception {
124 | List elements = new ArrayList<>();
125 | elements.add(new TestObject(1, 9.5, "a"));
126 | elements.add(new TestObject(2, 6.7, "ab"));
127 | int count = RxSQLite.get().insert(TestTable.TABLE, elements).test().values().get(0);
128 | assertEquals(2, count);
129 | }
130 |
131 | @Test
132 | public void testUpdateElement() throws Exception {
133 | List elements = new ArrayList<>();
134 | elements.add(new TestObject(1, 9.5, "a"));
135 | elements.add(new TestObject(2, 6.7, "ab"));
136 | SQLite.get().insert(TestTable.TABLE, elements);
137 |
138 | int count = RxSQLite.get().update(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 2), new TestObject(2, 6.7, "abc")).test().values().get(0);
139 | assertEquals(1, count);
140 |
141 | RxSQLite.get().querySingle(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 2))
142 | .subscribe(new Consumer() {
143 | @Override
144 | public void accept(TestObject testObject) {
145 | Assert.assertEquals("abc", testObject.getText());
146 | }
147 | }, new Consumer() {
148 | @Override
149 | public void accept(Throwable throwable) {
150 | fail();
151 | }
152 | });
153 | }
154 |
155 | @Test
156 | public void testDeleteElement() throws Exception {
157 | List elements = new ArrayList<>();
158 | elements.add(new TestObject(1, 9.5, "a"));
159 | elements.add(new TestObject(2, 6.7, "ab"));
160 | SQLite.get().insert(TestTable.TABLE, elements);
161 |
162 | int count = RxSQLite.get().delete(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 2)).test().values().get(0);
163 | assertEquals(1, count);
164 | }
165 |
166 | @Test
167 | public void testClearTable() throws Exception {
168 | List elements = new ArrayList<>();
169 | elements.add(new TestObject(1, 9.5, "a"));
170 | elements.add(new TestObject(2, 6.7, "ab"));
171 | elements.add(new TestObject(3, 8.2, "abc"));
172 | SQLite.get().insert(TestTable.TABLE, elements);
173 |
174 | int count = RxSQLite.get().delete(TestTable.TABLE).test().values().get(0);
175 | assertEquals(3, count);
176 | }
177 |
178 | @SuppressWarnings("unchecked")
179 | @Test
180 | public void testObserveTableChange() throws Exception {
181 | SQLite.get().enableAutomaticNotifications();
182 |
183 | Consumer action = Mockito.mock(Consumer.class);
184 | Mockito.doNothing().when(action).accept(anyBoolean());
185 | Disposable disposable = RxSQLite.get().observeChanges(TestTable.TABLE).subscribe(action);
186 |
187 | SQLite.get().insert(TestTable.TABLE, new TestObject(10010, 6.4, "changes"));
188 | Thread.sleep(300);
189 | Mockito.verify(action).accept(true);
190 |
191 | Mockito.reset(action);
192 | disposable.dispose();
193 |
194 | SQLite.get().delete(TestTable.TABLE);
195 | Thread.sleep(300);
196 | Mockito.verifyNoMoreInteractions(action);
197 | }
198 |
199 | @SuppressWarnings("unchecked")
200 | @Test
201 | public void testObserveTableChangeWithData() throws Exception {
202 | SQLite.get().enableAutomaticNotifications();
203 |
204 | Consumer> action = Mockito.mock(Consumer.class);
205 | Mockito.doNothing().when(action).accept(anyListOf(TestObject.class));
206 | Disposable disposable = RxSQLite.get().observeChanges(TestTable.TABLE).withQuery().subscribe(action);
207 |
208 | SQLite.get().insert(TestTable.TABLE, new TestObject(10410, 8.9, "ca'pcj;s;vhjvksf;bgd"));
209 | Thread.sleep(300);
210 | Mockito.verify(action).accept(anyListOf(TestObject.class));
211 |
212 | Mockito.reset(action);
213 | disposable.dispose();
214 |
215 | SQLite.get().delete(TestTable.TABLE);
216 | Thread.sleep(300);
217 | Mockito.verifyNoMoreInteractions(action);
218 | }
219 |
220 | @SuppressWarnings("unchecked")
221 | @Test
222 | public void testObserveTableChangeWithDataAndQuery() throws Exception {
223 | SQLite.get().enableAutomaticNotifications();
224 |
225 | Consumer> action = Mockito.mock(Consumer.class);
226 | Mockito.doNothing().when(action).accept(anyListOf(TestObject.class));
227 | RxSQLite.get().observeChanges(TestTable.TABLE)
228 | .withQuery(Where.create().lessThan(TestTable.RATING, 5))
229 | .subscribe(action);
230 |
231 | List list = new ArrayList<>();
232 | list.add(new TestObject(513, 1.6, "text44"));
233 | list.add(new TestObject(8, 7.6, "text2"));
234 | list.add(new TestObject(9, 4, "tex7"));
235 | SQLite.get().insert(TestTable.TABLE, list);
236 | Thread.sleep(300);
237 |
238 | ArgumentCaptor captor = ArgumentCaptor.forClass(List.class);
239 | Mockito.verify(action).accept(captor.capture());
240 | assertEquals(2, captor.getValue().size());
241 | }
242 |
243 | @After
244 | public void tearDown() throws Exception {
245 | SQLite.get().delete(TestTable.TABLE);
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/ru/arturvasilov/sqlite/core/SQLiteTest.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.core;
2 |
3 | import android.support.test.InstrumentationRegistry;
4 | import android.support.test.runner.AndroidJUnit4;
5 |
6 | import junit.framework.Assert;
7 |
8 | import org.junit.After;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.mockito.ArgumentCaptor;
13 | import org.mockito.Mockito;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | import ru.arturvasilov.sqlite.testutils.TestObject;
19 | import ru.arturvasilov.sqlite.testutils.TestTable;
20 |
21 | import static junit.framework.TestCase.assertEquals;
22 | import static junit.framework.TestCase.assertTrue;
23 | import static org.junit.Assert.assertNull;
24 | import static org.mockito.Matchers.anyListOf;
25 | import static org.mockito.Mockito.times;
26 |
27 | @RunWith(AndroidJUnit4.class)
28 | public class SQLiteTest {
29 |
30 | @Before
31 | public void setUp() throws Exception {
32 | SQLite.initialize(InstrumentationRegistry.getContext());
33 | SQLite.get().disableAutomaticNotifications();
34 | }
35 |
36 | @Test
37 | public void testInsertElement() throws Exception {
38 | TestObject test = new TestObject(5, 4.8, "aaaa");
39 | SQLite.get().insert(TestTable.TABLE, test);
40 | TestObject saved = SQLite.get().querySingle(TestTable.TABLE);
41 | assertEquals(test, saved);
42 | }
43 |
44 | @Test
45 | public void testInsertMultiple() throws Exception {
46 | List elements = new ArrayList<>();
47 | elements.add(new TestObject(1, 9.5, "a"));
48 | elements.add(new TestObject(2, 6.7, "ab"));
49 | elements.add(new TestObject(3, 8.2, "abc"));
50 | elements.add(new TestObject(4, 3.4, "abcd"));
51 | elements.add(new TestObject(5, 6.5, "abcde"));
52 | SQLite.get().insert(TestTable.TABLE, elements);
53 |
54 | int savedSize = SQLite.get().query(TestTable.TABLE).size();
55 | assertEquals(elements.size(), savedSize);
56 | }
57 |
58 | @Test
59 | public void testInsertReplacedPrimaryKey() throws Exception {
60 | List elements = new ArrayList<>();
61 | elements.add(new TestObject(11, 9.9, "a"));
62 | elements.add(new TestObject(12, 8.9, "ab"));
63 | SQLite.get().insert(TestTable.TABLE, elements);
64 |
65 | SQLite.get().insert(TestTable.TABLE, new TestObject(12, 5.7, "bc"));
66 | List savedElements = SQLite.get().query(TestTable.TABLE);
67 | assertEquals(elements.size(), savedElements.size());
68 | assertEquals("bc", savedElements.get(1).getText());
69 | }
70 |
71 | @Test
72 | public void testQueryWithSingleObject() throws Exception {
73 | List elements = new ArrayList<>();
74 | elements.add(new TestObject(11, 1.2, "a"));
75 | elements.add(new TestObject(12, 2.7, "ab"));
76 | SQLite.get().insert(TestTable.TABLE, elements);
77 |
78 | TestObject element = SQLite.get().querySingle(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 12));
79 | assertEquals(elements.get(1), element);
80 | }
81 |
82 | @Test
83 | public void testQueryNull() throws Exception {
84 | TestObject testObject = SQLite.get().querySingle(TestTable.TABLE);
85 | Assert.assertNull(testObject);
86 | }
87 |
88 | @Test
89 | public void testQueryList() throws Exception {
90 | List elements = new ArrayList<>();
91 | elements.add(new TestObject(110, 0, "a"));
92 | elements.add(new TestObject(111, 7.8, "ab"));
93 | elements.add(new TestObject(112, 9.6, "xaxka"));
94 | elements.add(new TestObject(113, 5.7, "ab"));
95 | elements.add(new TestObject(114, 6.8, "abc"));
96 | SQLite.get().insert(TestTable.TABLE, elements);
97 |
98 | List savedElements = SQLite.get().query(TestTable.TABLE, Where.create().equalTo(TestTable.TEXT, "ab"));
99 |
100 | assertEquals(2, savedElements.size());
101 | assertEquals(elements.get(1), savedElements.get(0));
102 | assertEquals(elements.get(3), savedElements.get(1));
103 | }
104 |
105 | @Test
106 | public void testEmptyElement() throws Exception {
107 | TestObject element = SQLite.get().querySingle(TestTable.TABLE);
108 | assertNull(element);
109 | }
110 |
111 | @Test
112 | public void testEmptyList() throws Exception {
113 | List elements = SQLite.get().query(TestTable.TABLE);
114 | assertTrue(elements.isEmpty());
115 | }
116 |
117 | @Test
118 | public void testInsertEmptyList() throws Exception {
119 | int count = SQLite.get().insert(TestTable.TABLE, new ArrayList());
120 | assertEquals(0, count);
121 | }
122 |
123 | @Test
124 | public void testUpdateRow() throws Exception {
125 | TestObject element = new TestObject(123321, 8.4, "abc");
126 | SQLite.get().insert(TestTable.TABLE, element);
127 |
128 | TestObject update = new TestObject(123321, 0.6, "xyz");
129 | int count = SQLite.get().update(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 123321), update);
130 | assertEquals(1, count);
131 |
132 | TestObject updated = SQLite.get().querySingle(TestTable.TABLE);
133 | assertEquals(update, updated);
134 | }
135 |
136 | @Test
137 | public void testNoUpdate() throws Exception {
138 | TestObject element = new TestObject(123321, 6, "abc");
139 | SQLite.get().insert(TestTable.TABLE, element);
140 |
141 | TestObject update = new TestObject(123322, 5.2, "xyz");
142 | int count = SQLite.get().update(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 1233212), update);
143 | assertEquals(0, count);
144 |
145 | TestObject notUpdated = SQLite.get().querySingle(TestTable.TABLE);
146 | assertEquals(element, notUpdated);
147 | }
148 |
149 | @Test
150 | public void testDeleteAll() throws Exception {
151 | List elements = new ArrayList<>();
152 | elements.add(new TestObject(11, 4, "a"));
153 | elements.add(new TestObject(12, 8.4, "ab"));
154 | SQLite.get().insert(TestTable.TABLE, elements);
155 |
156 | int count = SQLite.get().delete(TestTable.TABLE);
157 | assertEquals(2, count);
158 |
159 | elements = SQLite.get().query(TestTable.TABLE);
160 | assertTrue(elements.isEmpty());
161 | }
162 |
163 | @Test
164 | public void testDeleteByParameters() throws Exception {
165 | List elements = new ArrayList<>();
166 | elements.add(new TestObject(110, 7.1, "a"));
167 | elements.add(new TestObject(111, 8, "ab"));
168 | elements.add(new TestObject(112, 9, "xaxka"));
169 | elements.add(new TestObject(113, 6.9, "ab"));
170 | elements.add(new TestObject(114, 2.3, "abc"));
171 | SQLite.get().insert(TestTable.TABLE, elements);
172 |
173 | int count = SQLite.get().delete(TestTable.TABLE, Where.create().equalTo(TestTable.TEXT, "ab"));
174 | assertEquals(2, count);
175 |
176 | List leftElements = SQLite.get().query(TestTable.TABLE);
177 |
178 | assertEquals(3, leftElements.size());
179 | assertEquals(elements.get(0), leftElements.get(0));
180 | assertEquals(elements.get(2), leftElements.get(1));
181 | assertEquals(elements.get(4), leftElements.get(2));
182 | }
183 |
184 | @Test
185 | public void testSQLite() throws Exception {
186 | TestObject test = new TestObject(1, 9.3, "aaaa");
187 | SQLite.get().insert(TestTable.TABLE, test);
188 |
189 | List all = SQLite.get().query(TestTable.TABLE);
190 | assertEquals(1, all.size());
191 | assertTrue(test.equals(all.get(0)));
192 |
193 | test.setText("BBBBB");
194 | int rows = SQLite.get().update(TestTable.TABLE, Where.create().equalTo(TestTable.ID, 1), test);
195 |
196 | all = SQLite.get().query(TestTable.TABLE);
197 | assertEquals(1, rows);
198 | assertEquals(1, all.size());
199 | assertTrue(test.equals(all.get(0)));
200 |
201 | SQLite.get().delete(TestTable.TABLE);
202 |
203 | assertTrue(SQLite.get().query(TestTable.TABLE).isEmpty());
204 | }
205 |
206 | @Test
207 | public void testObserveTableChange() throws Exception {
208 | SQLite.get().enableAutomaticNotifications();
209 |
210 | BasicTableObserver observer = Mockito.mock(BasicTableObserver.class);
211 | Mockito.doNothing().when(observer).onTableChanged();
212 | SQLite.get().registerObserver(TestTable.TABLE, observer);
213 |
214 | SQLite.get().insert(TestTable.TABLE, new TestObject(5, 6, "text"));
215 | SQLite.get().delete(TestTable.TABLE);
216 | Thread.sleep(300);
217 |
218 | Mockito.verify(observer, times(2)).onTableChanged();
219 |
220 | Mockito.reset(observer);
221 | SQLite.get().unregisterObserver(observer);
222 | SQLite.get().insert(TestTable.TABLE, new TestObject(5, 6, "text"));
223 | Thread.sleep(300);
224 | Mockito.verifyNoMoreInteractions(observer);
225 | }
226 |
227 | @SuppressWarnings("unchecked")
228 | @Test
229 | public void testObserveTableChangeWithData() throws Exception {
230 | SQLite.get().enableAutomaticNotifications();
231 |
232 | ContentTableObserver observer = Mockito.mock(ContentTableObserver.class);
233 | Mockito.doNothing().when(observer).onTableChanged(anyListOf(TestObject.class));
234 | SQLite.get().registerObserver(TestTable.TABLE, observer);
235 |
236 | SQLite.get().insert(TestTable.TABLE, new TestObject(5, 9.7, "text"));
237 | Thread.sleep(300);
238 | Mockito.verify(observer).onTableChanged(anyListOf(TestObject.class));
239 |
240 | Mockito.reset(observer);
241 | SQLite.get().unregisterObserver(observer);
242 | SQLite.get().delete(TestTable.TABLE);
243 | Thread.sleep(300);
244 | Mockito.verifyNoMoreInteractions(observer);
245 | }
246 |
247 | @SuppressWarnings("unchecked")
248 | @Test
249 | public void testObserveTableChangeWithDataAndQuery() throws Exception {
250 | SQLite.get().enableAutomaticNotifications();
251 |
252 | ContentTableObserver observer = Mockito.mock(ContentTableObserver.class);
253 | Mockito.doNothing().when(observer).onTableChanged(anyListOf(TestObject.class));
254 | SQLite.get().registerObserver(TestTable.TABLE, observer, Where.create().equalTo(TestTable.ID, 5));
255 |
256 | List list = new ArrayList<>();
257 | list.add(new TestObject(5, 9.7, "text"));
258 | list.add(new TestObject(6, 8, "text2"));
259 | SQLite.get().insert(TestTable.TABLE, list);
260 | Thread.sleep(300);
261 |
262 | ArgumentCaptor captor = ArgumentCaptor.forClass(List.class);
263 | Mockito.verify(observer).onTableChanged(captor.capture());
264 | assertEquals(1, captor.getValue().size());
265 | }
266 |
267 | @SuppressWarnings("unchecked")
268 | @Test
269 | public void testControlAutomaticUpdates() throws Exception {
270 | ContentTableObserver observer = Mockito.mock(ContentTableObserver.class);
271 | Mockito.doNothing().when(observer).onTableChanged(anyListOf(TestObject.class));
272 | SQLite.get().registerObserver(TestTable.TABLE, observer, Where.create().equalTo(TestTable.ID, 100));
273 |
274 | SQLite.get().insert(TestTable.TABLE, new TestObject(1000, 8.8, "None"));
275 | Thread.sleep(300);
276 |
277 | Mockito.verifyNoMoreInteractions(observer);
278 |
279 | SQLite.get().enableAutomaticNotifications();
280 | SQLite.get().delete(TestTable.TABLE);
281 | Thread.sleep(300);
282 |
283 | ArgumentCaptor captor = ArgumentCaptor.forClass(List.class);
284 | Mockito.verify(observer).onTableChanged(captor.capture());
285 | assertEquals(0, captor.getValue().size());
286 | }
287 |
288 | @SuppressWarnings("unchecked")
289 | @Test
290 | public void testManualNotify() throws Exception {
291 | ContentTableObserver observer = Mockito.mock(ContentTableObserver.class);
292 | Mockito.doNothing().when(observer).onTableChanged(anyListOf(TestObject.class));
293 | SQLite.get().registerObserver(TestTable.TABLE, observer, Where.create().equalTo(TestTable.ID, 1));
294 |
295 | SQLite.get().insert(TestTable.TABLE, new TestObject(1, 3.2, "hello"));
296 | SQLite.get().notifyTableChanged(TestTable.TABLE);
297 | Thread.sleep(300);
298 | Mockito.verify(observer).onTableChanged(anyListOf(TestObject.class));
299 | }
300 |
301 | @After
302 | public void tearDown() throws Exception {
303 | SQLite.get().delete(TestTable.TABLE);
304 | }
305 | }
--------------------------------------------------------------------------------
/app/src/main/java/ru/arturvasilov/sqlite/core/SQLite.java:
--------------------------------------------------------------------------------
1 | package ru.arturvasilov.sqlite.core;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.ContentValues;
5 | import android.content.Context;
6 | import android.database.ContentObserver;
7 | import android.database.Cursor;
8 | import android.net.Uri;
9 | import android.support.annotation.NonNull;
10 | import android.support.annotation.Nullable;
11 |
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | import ru.arturvasilov.sqlite.utils.SQLiteUtils;
16 |
17 | /**
18 | * @author Artur Vasilov
19 | */
20 | public class SQLite {
21 |
22 | static {
23 | System.loadLibrary("sqliteX");
24 | }
25 |
26 | private static SQLite sSQLite;
27 |
28 | private final Context mContext;
29 |
30 | private final Observers mObservers;
31 |
32 | private boolean mIsAutomaticNotificationsEnabled;
33 |
34 | private SQLite(@NonNull Context context) {
35 | mContext = context;
36 | mObservers = new Observers();
37 | mIsAutomaticNotificationsEnabled = false;
38 | }
39 |
40 | /**
41 | * This method creates singleton instance of the SQLite and allows you to use non-parametrized method
42 | * {@link SQLite#get()} at any place of your app.
43 | *
44 | * Typically you will call this method in your {@link android.app.Application} class like this:
45 | *
46 | * {@code
47 | * public class MyApplication extends Application {
48 | * @Override
49 | * public void onCreate() {
50 | * super.onCreate();
51 | * SQLite.initialize(this);
52 | * }
53 | * }
54 | *
55 | *
56 | * @param context - any context to access the content provider
57 | * @return created singleton instance of SQLite
58 | */
59 | @NonNull
60 | public static SQLite initialize(@NonNull Context context) {
61 | SQLite sqLite = sSQLite;
62 | if (sqLite == null) {
63 | synchronized (SQLite.class) {
64 | sqLite = sSQLite;
65 | if (sqLite == null) {
66 | sqLite = sSQLite = new SQLite(context.getApplicationContext());
67 | }
68 | }
69 | }
70 | return sqLite;
71 | }
72 |
73 | /**
74 | * You must be sure that you've initialized SQLite instance by {@link SQLite#initialize(Context)}
75 | * or {@link IllegalStateException} will be thrown.
76 | *
77 | * @return singleton instance of SQLite
78 | */
79 | @NonNull
80 | public static SQLite get() {
81 | if (sSQLite == null) {
82 | throw new IllegalStateException("You should call initialize(Context) first, to initialize the database");
83 | }
84 | return sSQLite;
85 | }
86 |
87 | /**
88 | * This methods returns all rows of the table.
89 | * If you want to specify query parameters, you should call {@link SQLite#query(Table, Where)}
90 | *
91 | * @param table - table you want to query
92 | * @return all rows from the table as a list of table class objects.
93 | */
94 | @NonNull
95 | public List query(@NonNull Table table) {
96 | return query(table, Where.create());
97 | }
98 |
99 | /**
100 | * @param table - table you want to query
101 | * @param where - arguments for query
102 | * @return all rows from the table which satisfy where parameter as a list of table class objects.
103 | */
104 | @NonNull
105 | public List query(@NonNull Table table, @NonNull Where where) {
106 | List list = new ArrayList<>();
107 |
108 | Cursor cursor = mContext.getContentResolver().query(table.getUri(), null, where.where(), where.whereArgs(), null);
109 | try {
110 | if (SQLiteUtils.isEmptyCursor(cursor)) {
111 | return list;
112 | }
113 | do {
114 | T t = table.fromCursor(cursor);
115 | list.add(t);
116 | } while (cursor.moveToNext());
117 | return list;
118 | } finally {
119 | SQLiteUtils.safeCloseCursor(cursor);
120 | }
121 | }
122 |
123 | /**
124 | * Query for the first object in the table
125 | *
126 | * @param table - table you want to query
127 | * @return first object from the table or null if table is empty
128 | */
129 | @Nullable
130 | public T querySingle(@NonNull Table table) {
131 | return querySingle(table, Where.create());
132 | }
133 |
134 | /**
135 | * Query for the first object in the table which satisfy where parameter
136 | *
137 | * @param table - table you want to query
138 | * @param where - arguments for query
139 | * @return first object from the table or null if table is empty
140 | */
141 | @Nullable
142 | public T querySingle(@NonNull Table table, @NonNull Where where) {
143 | Cursor cursor = mContext.getContentResolver().query(table.getUri(), null, where.where(), where.whereArgs(), where.limit());
144 | try {
145 | if (SQLiteUtils.isEmptyCursor(cursor)) {
146 | return null;
147 | }
148 | return table.fromCursor(cursor);
149 | } finally {
150 | SQLiteUtils.safeCloseCursor(cursor);
151 | }
152 | }
153 |
154 | /**
155 | * This method inserts object to the table. In cases of conflict the old object will be replaced by the new one.
156 | *
157 | * @param table - table in which you want to insert object
158 | * @param object - object to insert in database
159 | * @return uri of inserted object. In most cases you won't use it.
160 | */
161 | @Nullable
162 | public Uri insert(@NonNull Table table, @NonNull T object) {
163 | Uri uri = mContext.getContentResolver().insert(table.getUri(), table.toValues(object));
164 | if (uri != null && mIsAutomaticNotificationsEnabled) {
165 | notifyTableChanged(table);
166 | }
167 | return uri;
168 | }
169 |
170 | /**
171 | * This method inserts objects to the table. In cases of conflict the old objects will be replaced by the new ones.
172 | *
173 | * @param table - table in which you want to insert objects
174 | * @param objects - list of objects to insert in database
175 | * @return count of successfully inserted objects
176 | */
177 | public int insert(@NonNull Table table, @NonNull List objects) {
178 | ContentValues[] values = new ContentValues[objects.size()];
179 | for (int i = 0; i < objects.size(); i++) {
180 | values[i] = table.toValues(objects.get(i));
181 | }
182 | int count = mContext.getContentResolver().bulkInsert(table.getUri(), values);
183 | if (count > 0 && mIsAutomaticNotificationsEnabled) {
184 | notifyTableChanged(table);
185 | }
186 | return count;
187 | }
188 |
189 | /**
190 | * This method clears the table passed as a parameters
191 | * If you want to specify parameters for deleting, you should call {@link SQLite#delete(Table, Where)}
192 | *
193 | * @param table - table you want to clear
194 | * @return count of deleted rows
195 | */
196 | public int delete(@NonNull Table table) {
197 | return delete(table, Where.create());
198 | }
199 |
200 | /**
201 | * This method deletes all rows in the table which satisfy where parameter
202 | *
203 | * @param table - table from which you want to delete rows
204 | * @param where - arguments for delete rows from the table
205 | * @return count of deleted objects
206 | */
207 | public int delete(@NonNull Table table, @NonNull Where where) {
208 | int count = mContext.getContentResolver().delete(table.getUri(), where.where(), where.whereArgs());
209 | if (count > 0 && mIsAutomaticNotificationsEnabled) {
210 | notifyTableChanged(table);
211 | }
212 | return count;
213 | }
214 |
215 | /**
216 | * This method updates all rows in the table which satisfy where parameter
217 | *
218 | * @param table - table where you want to update rows
219 | * @param where - arguments for update rows in the table
220 | * @param newObject - object which will replace all rows which satisfy where parameter
221 | * @return count of updated objects
222 | */
223 | public int update(@NonNull Table table, @NonNull Where where, @NonNull T newObject) {
224 | int count = mContext.getContentResolver().update(table.getUri(), table.toValues(newObject),
225 | where.where(), where.whereArgs());
226 | if (count > 0 && mIsAutomaticNotificationsEnabled) {
227 | notifyTableChanged(table);
228 | }
229 | return count;
230 | }
231 |
232 | /**
233 | * Attaches callback to get notified about changes in certain table
234 | * For more information take a look at {@link BasicTableObserver}
235 | *
236 | * @param table - table to observe changes in
237 | * @param observer - listener which will be called when table changes
238 | */
239 | public void registerObserver(@NonNull Table table, @NonNull final BasicTableObserver observer) {
240 | mObservers.registerObserver(mContext, table, observer);
241 | }
242 |
243 | /**
244 | * Attaches callback to get notified about changes in certain table and query all rows
245 | * For more information take a look at {@link ContentTableObserver}
246 | *
247 | * {@link SQLite#registerObserver(Table, ContentTableObserver, Where)}
248 | *
249 | * @param table - table to observe changes in
250 | * @param observer - listener which will be called when table changes
251 | */
252 | public void registerObserver(@NonNull Table table, @NonNull final ContentTableObserver observer) {
253 | mObservers.registerObserver(mContext, table, observer, Where.create());
254 | }
255 |
256 | /**
257 | * Attaches callback to get notified about changes in certain table and query rows which satisfies where parameter
258 | * For more information take a look at {@link ContentTableObserver}
259 | *
260 | * @param table - table to observe changes in
261 | * @param observer - listener which will be called when table changes
262 | * @param where - arguments for query
263 | */
264 | public void registerObserver(@NonNull Table table, @NonNull ContentTableObserver observer, @NonNull Where where) {
265 | mObservers.registerObserver(mContext, table, observer, where);
266 | }
267 |
268 | /**
269 | * Detaches listener from observing changes in database
270 | *
271 | * @param observer - listener to detach from ContentProvider notifications
272 | */
273 | public void unregisterObserver(@NonNull BasicTableObserver observer) {
274 | mObservers.unregisterObserver(mContext, observer);
275 | }
276 |
277 | /**
278 | * Detaches listener from observing changes in database
279 | *
280 | * @param observer - listener to detach from ContentProvider notifications
281 | */
282 | public void unregisterObserver(@NonNull ContentTableObserver observer) {
283 | mObservers.unregisterObserver(mContext, observer);
284 | }
285 |
286 | /**
287 | * Enables {@link android.content.ContentResolver#notifyChange(Uri, ContentObserver)}
288 | * automatic calls when table changes from SQLite methods.
289 | *
290 | * By default automatic notifications are disabled to let you have more control on them.
291 | */
292 | public void enableAutomaticNotifications() {
293 | mIsAutomaticNotificationsEnabled = true;
294 | }
295 |
296 | /**
297 | * Disables {@link android.content.ContentResolver#notifyChange(Uri, ContentObserver)}
298 | * automatic calls when table changes from SQLite methods.
299 | *
300 | * By default automatic notifications are disabled.
301 | */
302 | public void disableAutomaticNotifications() {
303 | mIsAutomaticNotificationsEnabled = false;
304 | }
305 |
306 | /**
307 | * Notifies all observers about table change. This call will invoke notifications for all observers,
308 | * which are registered using one of the register method:
309 | * {@link SQLite#registerObserver(Table, BasicTableObserver)}
310 | * {@link SQLite#registerObserver(Table, ContentTableObserver)}
311 | * {@link SQLite#registerObserver(Table, ContentTableObserver, Where)}
312 | *
313 | * @param table - uri from this table will be used for notification for observers
314 | */
315 | public void notifyTableChanged(@NonNull Table table) {
316 | mContext.getContentResolver().notifyChange(table.getUri(), null);
317 | }
318 |
319 | /**
320 | * Returns the instance of ContentResolver, which is used for all operations in {@link SQLite} class
321 | *
322 | * You may use it to work with data directly, but you should be careful, since you can loose
323 | * features like automatic notifications about table changes.
324 | *
325 | * @return instance of ContentResolver which is associated with {@link SQLiteContentProvider}
326 | */
327 | @NonNull
328 | public ContentResolver getContentResolver() {
329 | return mContext.getContentResolver();
330 | }
331 | }
332 |
--------------------------------------------------------------------------------