Skip to content

Commit

Permalink
Use Singles instead of Observables when only one value is returned. E…
Browse files Browse the repository at this point in the history
…mit List items instead of List. Replace Ble.start/stopScan() with scan().
  • Loading branch information
patloew committed Mar 17, 2016
1 parent 5350cb8 commit 6bfee7f
Show file tree
Hide file tree
Showing 58 changed files with 1,974 additions and 1,137 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Version 1.2.0

* BREAKING CHANGE: The lib now uses Singles instead of Observables if only one item is emitted.
* BREAKING CHANGE: Observables, which previously emitted a single List, now emit the items of the list.
* BREAKING CHANGE: `RxFit.Ble.startScan(...)` and `stopScan(...)` was removed and replaced by `RxFit.Ble.scan(...)`.
* Added RxFit.OnExceptionResumeNext.Single Transformer.

## Version 1.1.1

* Fix for multiple subscribers on RxFit Observables.
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Reactive Fit API Library for Android

[![Build Status](https://travis-ci.org/patloew/RxFit.svg?branch=master)](https://travis-ci.org/patloew/RxFit) [ ![Download](https://api.bintray.com/packages/patloew/maven/com.patloew.rxfit/images/download.svg) ](https://bintray.com/patloew/maven/com.patloew.rxfit/_latestVersion) [![API](https://img.shields.io/badge/API-9%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=9)
[![Build Status](https://travis-ci.org/patloew/RxFit.svg?branch=master)](https://travis-ci.org/patloew/RxFit) [ ![Download](https://api.bintray.com/packages/patloew/maven/RxFit/images/download.svg) ](https://bintray.com/patloew/maven/RxFit/_latestVersion) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-RxFit-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/3252) [![API](https://img.shields.io/badge/API-9%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=9)

This library wraps the Fit API in [RxJava](https://github.com/ReactiveX/RxJava) Observables. No more managing GoogleApiClients! Also, the authorization process for using fitness data is handled by the lib.
This library wraps the Fit API in [RxJava](https://github.com/ReactiveX/RxJava) Observables and Singles. No more managing GoogleApiClients! Also, the authorization process for using fitness data is handled by the lib.

# Usage

Expand All @@ -25,15 +25,17 @@ DataReadRequest dataReadRequest = new DataReadRequest.Builder()
.build();

RxFit.History.read(dataReadRequest)
.flatMap(dataReadResult -> Observable.from(dataReadResult.getBuckets()))
.flatMapObservable(dataReadResult -> Observable.from(dataReadResult.getBuckets()))
.subscribe(bucket -> {
/* do something */
});
```

An `OnExceptionResumeNext` Transformer is available in the lib, which resumes with another Single/Observable when an Exception is thrown, except when the exception was a GoogleAPIConnectionException which was caused by an unresolved resolution.

An optional global default timeout for all Fit API requests made through the library can be set via `RxFit.setDefaultTimeout(...)`. In addition, timeouts can be set when creating a new Observable by providing timeout parameters, e.g. `RxFit.History.read(dataReadRequest, 15, TimeUnit.SECONDS)`. These parameters override the default timeout. When a timeout occurs, a StatusException is provided via `onError()`. The RxJava timeout operators can be used instead, but these do not cancel the Fit API request immediately.

You can also obtain an `Observable<GoogleApiClient>`, which connects on subscribe and disconnects on unsubscribe via `GoogleAPIClientObservable.create(...)`.
You can also obtain a `Single<GoogleApiClient>`, which connects on subscribe and disconnects on unsubscribe via `GoogleAPIClientSingle.create(...)`.

The following Exceptions are thrown in the lib and provided via `onError()`:

Expand All @@ -51,7 +53,7 @@ A basic sample app is available in the `sample` project. You need to create an O
The lib is available on jCenter. Add the following to your `build.gradle`:

dependencies {
compile 'com.patloew.rxfit:rxfit:1.1.1'
compile 'com.patloew.rxfit:rxfit:1.2.0'
}

# Credits
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0-beta6'
classpath 'com.android.tools.build:gradle:2.0.0-beta7'

classpath 'me.tatarka:gradle-retrolambda:3.3.0-beta4'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip
7 changes: 3 additions & 4 deletions library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apply plugin: 'com.jfrog.bintray'
apply plugin: 'com.github.dcendents.android-maven'

group = 'com.patloew.rxfit'
version = '1.1.1'
version = '1.2.0'
project.archivesBaseName = 'rxfit'

android {
Expand All @@ -13,8 +13,8 @@ android {
defaultConfig {
minSdkVersion 9
targetSdkVersion 23
versionCode 3
versionName "1.1.1"
versionCode 4
versionName "1.2.0"
}
buildTypes {
release {
Expand All @@ -29,7 +29,6 @@ android {

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.2.0'
compile 'io.reactivex:rxjava:1.1.1'
compile 'com.google.android.gms:play-services-fitness:8.4.0'

Expand Down
122 changes: 25 additions & 97 deletions library/src/main/java/com/patloew/rxfit/BaseObservable.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,13 @@
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.Api;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Result;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Scope;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.functions.Action0;
import rx.subscriptions.Subscriptions;
Expand All @@ -45,53 +39,24 @@
* FILE MODIFIED by Patrick Löwenstein, 2016
*
*/
public abstract class BaseObservable<T> implements Observable.OnSubscribe<T> {
private static final Set<BaseObservable> observableSet = new HashSet<>();

private final Context ctx;
private final Api<? extends Api.ApiOptions.NotRequiredOptions>[] services;
private final Scope[] scopes;
public abstract class BaseObservable<T> extends BaseRx<T> implements Observable.OnSubscribe<T> {
private final boolean handleResolution;
private final Long timeoutTime;
private final TimeUnit timeoutUnit;

private final HashMap<GoogleApiClient, Subscriber<? super T>> subscriptionInfoHashMap = new HashMap<>();

protected BaseObservable(@NonNull RxFit rxFit, Long timeout, TimeUnit timeUnit) {
this.ctx = rxFit.getContext();
this.services = rxFit.getApis();
this.scopes = rxFit.getScopes();
super(rxFit, timeout, timeUnit);
handleResolution = true;

if(timeout != null && timeUnit != null) {
this.timeoutTime = timeout;
this.timeoutUnit = timeUnit;
} else {
this.timeoutTime = RxFit.getDefaultTimeout();
this.timeoutUnit = RxFit.getDefaultTimeoutUnit();
}
}

protected BaseObservable(@NonNull Context ctx, @NonNull Api<? extends Api.ApiOptions.NotRequiredOptions>[] services, Scope[] scopes) {
this.ctx = ctx;
this.services = services;
this.scopes = scopes;
super(ctx, services, scopes);
handleResolution = false;
timeoutTime = null;
timeoutUnit = null;
}

protected final <T extends Result> void setupFitnessPendingResult(PendingResult<T> pendingResult, ResultCallback<? super T> resultCallback) {
if(timeoutTime != null && timeoutUnit != null) {
pendingResult.setResultCallback(resultCallback, timeoutTime, timeoutUnit);
} else {
pendingResult.setResultCallback(resultCallback);
}
}

@Override
public final void call(Subscriber<? super T> subscriber) {
final GoogleApiClient apiClient = createApiClient(subscriber);
final GoogleApiClient apiClient = createApiClient(new ApiClientConnectionCallbacks(subscriber));
subscriptionInfoHashMap.put(apiClient, subscriber);

try {
Expand All @@ -113,63 +78,46 @@ public void call() {
}));
}

protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, Subscriber<? super T> subscriber);

GoogleApiClient createApiClient(Subscriber<? super T> subscriber) {

ApiClientConnectionCallbacks apiClientConnectionCallbacks = new ApiClientConnectionCallbacks(subscriber);

GoogleApiClient.Builder apiClientBuilder = new GoogleApiClient.Builder(ctx);


for (Api<? extends Api.ApiOptions.NotRequiredOptions> service : services) {
apiClientBuilder.addApi(service);
}

if(scopes != null) {
for (Scope scope : scopes) {
apiClientBuilder.addScope(scope);
protected final void handleResolutionResult(int resultCode, ConnectionResult connectionResult) {
for (Map.Entry<GoogleApiClient, Subscriber<? super T>> entry : subscriptionInfoHashMap.entrySet()) {
if (!entry.getValue().isUnsubscribed()) {
if (resultCode == Activity.RESULT_OK) {
try {
entry.getKey().connect();
} catch (Throwable ex) {
entry.getValue().onError(ex);
}
} else {
entry.getValue().onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient, resolution was not successful.", connectionResult));
}
}
}

apiClientBuilder.addConnectionCallbacks(apiClientConnectionCallbacks);
apiClientBuilder.addOnConnectionFailedListener(apiClientConnectionCallbacks);

GoogleApiClient apiClient = apiClientBuilder.build();

apiClientConnectionCallbacks.setClient(apiClient);

return apiClient;

}

protected void onUnsubscribed(GoogleApiClient locationClient) { }

protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, Observer<? super T> observer);

private class ApiClientConnectionCallbacks implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
protected class ApiClientConnectionCallbacks extends BaseRx.ApiClientConnectionCallbacks {

final private Observer<? super T> observer;
final protected Subscriber<? super T> subscriber;

private GoogleApiClient apiClient;

private ApiClientConnectionCallbacks(Observer<? super T> observer) {
this.observer = observer;
private ApiClientConnectionCallbacks(Subscriber<? super T> subscriber) {
this.subscriber = subscriber;
}

@Override
public void onConnected(Bundle bundle) {
try {
onGoogleApiClientReady(apiClient, observer);
onGoogleApiClientReady(apiClient, subscriber);
} catch (Throwable ex) {
observer.onError(ex);
subscriber.onError(ex);
}
}

@Override
public void onConnectionSuspended(int cause) {
observer.onError(new GoogleAPIConnectionSuspendedException(cause));
subscriber.onError(new GoogleAPIConnectionSuspendedException(cause));
}

@Override
Expand All @@ -184,32 +132,12 @@ public void onConnectionFailed(ConnectionResult connectionResult) {
ctx.startActivity(intent);
}
} else {
observer.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", connectionResult));
subscriber.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", connectionResult));
}
}

public void setClient(GoogleApiClient client) {
this.apiClient = client;
}
}

static void onResolutionResult(int resultCode, ConnectionResult connectionResult) {
for(BaseObservable observable : observableSet) {
for (Map.Entry<GoogleApiClient, Subscriber> entry : (Set<Map.Entry<GoogleApiClient, Subscriber>>) observable.subscriptionInfoHashMap.entrySet()) {
if (!entry.getValue().isUnsubscribed()) {
if (resultCode == Activity.RESULT_OK) {
try {
entry.getKey().connect();
} catch (Throwable ex) {
entry.getValue().onError(ex);
}
} else {
entry.getValue().onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient, resolution was not successful.", connectionResult));
}
}
}
}

observableSet.clear();
}
}
Loading

0 comments on commit 6bfee7f

Please sign in to comment.