Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add StorIOContentResolver get().numberOfResults()! #534

Merged
merged 1 commit into from Oct 1, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -3,6 +3,7 @@
import android.support.annotation.NonNull;

import com.pushtorefresh.storio.contentresolver.StorIOContentResolver;
import com.pushtorefresh.storio.contentresolver.queries.Query;
import com.pushtorefresh.storio.operations.PreparedOperation;

/**
Expand All @@ -15,8 +16,12 @@ public abstract class PreparedGet<Result> implements PreparedOperation<Result> {
@NonNull
protected final StorIOContentResolver storIOContentResolver;

PreparedGet(@NonNull StorIOContentResolver storIOContentResolver) {
@NonNull
protected final Query query;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice :)


PreparedGet(@NonNull StorIOContentResolver storIOContentResolver, @NonNull Query query) {
this.storIOContentResolver = storIOContentResolver;
this.query = query;
}

/**
Expand Down Expand Up @@ -52,5 +57,15 @@ public PreparedGetCursor.Builder cursor() {
public <T> PreparedGetListOfObjects.Builder<T> listOfObjects(@NonNull Class<T> type) {
return new PreparedGetListOfObjects.Builder<T>(storIOContentResolver, type);
}

/**
* Returns builder for Get Operation that returns number of results.
*
* @return builder for Get Operation that returns number of results.
*/
@NonNull
public PreparedGetNumberOfResults.Builder numberOfResults() {
return new PreparedGetNumberOfResults.Builder(storIOContentResolver);
}
}
}
Expand Up @@ -25,17 +25,13 @@
*/
public final class PreparedGetCursor extends PreparedGet<Cursor> {

@NonNull
private final Query query;

@NonNull
private final GetResolver<Cursor> getResolver;

PreparedGetCursor(@NonNull StorIOContentResolver storIOContentResolver,
@NonNull GetResolver<Cursor> getResolver,
@NonNull Query query) {
super(storIOContentResolver);
this.query = query;
super(storIOContentResolver, query);
this.getResolver = getResolver;
}

Expand Down
Expand Up @@ -36,19 +36,15 @@ public final class PreparedGetListOfObjects<T> extends PreparedGet<List<T>> {
@NonNull
private final Class<T> type;

@NonNull
private final Query query;

@Nullable
private final GetResolver<T> explicitGetResolver;

PreparedGetListOfObjects(@NonNull StorIOContentResolver storIOContentResolver,
@NonNull Class<T> type,
@NonNull Query query,
@Nullable GetResolver<T> explicitGetResolver) {
super(storIOContentResolver);
super(storIOContentResolver, query);
this.type = type;
this.query = query;
this.explicitGetResolver = explicitGetResolver;
}

Expand Down Expand Up @@ -140,7 +136,7 @@ public Observable<List<T>> createObservable() {
}

/**
* Compile-time safe part of builder for {@link PreparedGetListOfObjects}.
* Builder for {@link PreparedGetListOfObjects}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, hadn't saw this typo, thanks!

*
* @param <T> type of objects for query.
*/
Expand Down
@@ -0,0 +1,175 @@
package com.pushtorefresh.storio.contentresolver.operations.get;

import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;

import com.pushtorefresh.storio.StorIOException;
import com.pushtorefresh.storio.contentresolver.StorIOContentResolver;
import com.pushtorefresh.storio.contentresolver.queries.Query;
import com.pushtorefresh.storio.operations.internal.MapSomethingToExecuteAsBlocking;
import com.pushtorefresh.storio.operations.internal.OnSubscribeExecuteAsBlocking;

import rx.Observable;
import rx.schedulers.Schedulers;

import static com.pushtorefresh.storio.internal.Checks.checkNotNull;
import static com.pushtorefresh.storio.internal.Environment.throwExceptionIfRxJavaIsNotAvailable;

public class PreparedGetNumberOfResults extends PreparedGet<Integer> {

@NonNull
private final GetResolver<Integer> getResolver;

PreparedGetNumberOfResults(@NonNull StorIOContentResolver storIOContentResolver, @NonNull Query query, @NonNull GetResolver<Integer> getResolver) {
super(storIOContentResolver, query);
this.getResolver = getResolver;
}

/**
* Executes Get Operation immediately in current thread.
* <p>
* Notice: This is blocking I/O operation that should not be executed on the Main Thread,
* it can cause ANR (Activity Not Responding dialog), block the UI and drop animations frames.
* So please, call this method on some background thread. See {@link WorkerThread}.
*
* @return non-null {@link Integer} with number of results of the query.
*/
@WorkerThread
@NonNull
@Override
public Integer executeAsBlocking() {
final Cursor cursor;

try {
cursor = getResolver.performGet(storIOContentResolver, query);
try {
return getResolver.mapFromCursor(cursor);
} finally {
cursor.close();
}
} catch (Exception exception) {
throw new StorIOException(exception);
}
}

/**
* Creates "Hot" {@link Observable} which will be subscribed to changes of tables from query
* and will emit result each time change occurs.
* <p>
* First result will be emitted immediately after subscription,
* other emissions will occur only if changes of tables from query will occur during lifetime of
* the {@link Observable}.
* <dl>
* <dt><b>Scheduler:</b></dt>
* <dd>Operates on {@link Schedulers#io()}.</dd>
* </dl>
* <p>
* Please don't forget to unsubscribe from this {@link Observable} because
* it's "Hot" and endless.
*
* @return non-null {@link Observable} which will emit non-null
* number of results of the executed query and will be subscribed to changes of tables from query.
*/
@NonNull
@Override
public Observable<Integer> createObservable() {
throwExceptionIfRxJavaIsNotAvailable("createObservable()");

return storIOContentResolver
.observeChangesOfUri(query.uri()) // each change triggers executeAsBlocking
.map(MapSomethingToExecuteAsBlocking.newInstance(this))
.startWith(Observable.create(OnSubscribeExecuteAsBlocking.newInstance(this))) // start stream with first query result
.subscribeOn(Schedulers.io());
}

/**
* Builder for {@link PreparedGetNumberOfResults}.
*/
public static final class Builder {

@NonNull
private final StorIOContentResolver storIOContentResolver;

Builder(@NonNull StorIOContentResolver storIOContentResolver) {
this.storIOContentResolver = storIOContentResolver;
}

/**
* Required: Specifies query which will be passed to {@link StorIOContentResolver}
* to get list of objects.
*
* @param query non-null query.
* @return builder.
* @see Query
*/
@NonNull
public CompleteBuilder withQuery(@NonNull Query query) {
checkNotNull(query, "Please specify query");
return new CompleteBuilder(storIOContentResolver, query);
}
}

/**
* Compile-time safe part of builder for {@link PreparedGetNumberOfResults}.
*/
public static final class CompleteBuilder {

@NonNull
static final GetResolver<Integer> STANDARD_GET_RESOLVER = new DefaultGetResolver<Integer>() {
@NonNull
@Override
public Integer mapFromCursor(@NonNull Cursor cursor) {
return cursor.getCount();
}
};

@NonNull
private final StorIOContentResolver storIOContentResolver;

@NonNull
private final Query query;

@Nullable
private GetResolver<Integer> getResolver;

CompleteBuilder(@NonNull StorIOContentResolver storIOContentResolver, @NonNull Query query) {
this.storIOContentResolver = storIOContentResolver;
this.query = query;
}

/**
* Optional: Specifies resolver for Get Operation which can be used
* to provide custom behavior of Get Operation.
* <p>
*
* @param getResolver nullable resolver for Get Operation.
* @return builder.
*/
@NonNull
public CompleteBuilder withGetResolver(@Nullable GetResolver<Integer> getResolver) {
this.getResolver = getResolver;
return this;
}

/**
* Builds new instance of {@link PreparedGetNumberOfResults}.
*
* @return new instance of {@link PreparedGetNumberOfResults}.
*/
@NonNull
public PreparedGetNumberOfResults prepare() {
if (getResolver == null) {
getResolver = STANDARD_GET_RESOLVER;
}

return new PreparedGetNumberOfResults(
storIOContentResolver,
query,
getResolver
);
}
}

}
@@ -1,5 +1,6 @@
package com.pushtorefresh.storio.contentresolver.integration;

import android.content.ContentValues;
import android.database.Cursor;

import com.pushtorefresh.storio.contentresolver.BuildConfig;
Expand Down Expand Up @@ -165,4 +166,38 @@ public void getListOfObjectsAsObservableOnlyInitialValue() {
changesTestSubscriber.assertNoErrors();
changesTestSubscriber.assertValues(Changes.newInstance(TestItem.CONTENT_URI));
}

@Test
public void getNumberOfResults() {
TestSubscriber<Changes> changesTestSubscriber = new TestSubscriber<Changes>();

storIOContentResolver
.observeChangesOfUri(TestItem.CONTENT_URI)
.take(1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should take 8 changes here because you're inserting 8 items separately and ContentProvider throws 8 notifications.

That was the problem of our tests flakiness :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe bulkInsert?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, no problem, but don't forget to override it in ContentProvider because by default it'll just call insert in a loop and it'll create 8 notifications :)

.subscribe(changesTestSubscriber);

final int testItemsQuantity = 8;

ContentValues[] contentValues = new ContentValues[testItemsQuantity];
for (int i = 0; i < testItemsQuantity; ++i) {
TestItem testItemToInsert = TestItem.create(null, "value");
contentValues[i] = testItemToInsert.toContentValues();
}
contentResolver.bulkInsert(TestItem.CONTENT_URI, contentValues);

Integer numberOfResults = storIOContentResolver
.get()
.numberOfResults()
.withQuery(Query.builder()
.uri(TestItem.CONTENT_URI)
.build())
.prepare()
.executeAsBlocking();

assertThat(numberOfResults).isEqualTo(testItemsQuantity);

changesTestSubscriber.awaitTerminalEvent(60, SECONDS);
changesTestSubscriber.assertNoErrors();
changesTestSubscriber.assertValue(Changes.newInstance(TestItem.CONTENT_URI));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you have to check that 8 changes with same Uri were received

}
}
Expand Up @@ -4,6 +4,7 @@
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.NonNull;
Expand Down Expand Up @@ -81,6 +82,32 @@ public Uri insert(@NonNull Uri uri, @NonNull ContentValues values) {
throw new IllegalArgumentException("Unknown uri = " + uri);
}

@Override
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
switch (uriMatcher.match(uri)) {
case CODE_TEST_ITEM_MATCH:
SQLiteDatabase sqLiteDatabase = sqLiteOpenHelper.getWritableDatabase();
try {
sqLiteDatabase.beginTransaction();

for (ContentValues value : values) {
sqLiteDatabase.insert(
IntegrationSQLiteOpenHelper.TABLE_TEST_ITEMS,
null,
value);
}
sqLiteDatabase.setTransactionSuccessful();
} finally {
sqLiteDatabase.endTransaction();
}

getContext().getContentResolver().notifyChange(uri, null);

return values.length;
}
throw new IllegalArgumentException("Unknown uri = " + uri);
}

@Override
public int update(@NonNull Uri uri, @NonNull ContentValues values, @Nullable String selection,
@Nullable String[] selectionArgs) {
Expand Down