Giter VIP home page Giter VIP logo

reactive-billing's Introduction

Reactive Billing for Android

Cut the hassle when implementing in-app purchases on Android.
Reactive Billing is a lightweight reactive wrapper around In App Billing API v3 for Android.

Features

  • Reactive: Exposes the In App Billing service methods as Observable, allowing to implement easy asynchrounous callbacks and other Rx-related fun stuff.

  • No configuration: Doesn't require to implement activities onActivityResult(). It makes it super easy to implement in any architectures (activities/fragments, single activity, etc).

  • Lightweight: Only does what it's supposed to do, nothing more. It doesn't implement any logic related to the billing: purchase verification, storage for offline usage, etc. It is all up to you.

  • Convenient: Returns objects rather than bundles.

Version

Reactive Billing supports In App Billing API v3 only.
The current version (0.2) doesn't support subscriptions yet, but it's coming soon.

How does it work?

The Reactive Billing API is accessible through the singleton instance.

ReactiveBilling.getInstance(context);

The exposed methods are matching the methods of the AIDL billing service IInAppBillingService. With the difference that they return Observable objects, also taking care of connecting to the AIDL billing service.

For a complete setup example, please read the documentation below and check the sample project.

Response

Each call to the billing service will return a response object.
The response will match the structure of the original Bundle, containing at least a response code.

You can check the response codes in the documentation: In App Billing reference

onNext / onError

The subscriber will always receive onNext if the request to the billing service is executed successfully. But it doesn't mean that the response of the request is a success. You need to check the returned response code.

You can find all the response codes and their meaning in the documentation: In App Billing reference

The subscriber can also receive onError if an exception is thrown during the connection to the AIDL billing service (RemoteException). Reactive Billing is not doing any logic to catch the exception and the latter will be propagated to the subscriber.

Threading

Depending on which call and on the current Play Store cache, the billing service can trigger a synchronous network request. It is then recommended to implement the asynchronous reactive model when interacting with the service.

ReactiveBilling.getInstance(getContext())
	.getPurchases(PurchaseType.PRODUCT, null)
	.subscribeOn(Schedulers.io())
	.observeOn(AndroidSchedulers.mainThread())
	.subscribe(...)

Methods

Is Billing Supported

The Rx version of IInAppBillingService.isBillingSupported()

ReactiveBilling.getInstance(getContext()).isBillingSupported(PurchaseType.PRODUCT)
	.subscribeOn(Schedulers.io())
	.observeOn(AndroidSchedulers.mainThread())
	.subscribe(new Action1<Response>() {
		@Override
		public void call(Response response) {
			if(response.isSuccess()) {
				// in app billing is supported
			}
		}
	}, new Action1<Throwable>() {
		@Override
		public void call(Throwable throwable) {
		    
		}
	});

Get sku details

The Rx version of IInAppBillingService.getSkuDetails()

ReactiveBilling.getInstance(getContext())
	.getSkuDetails(PurchaseType.PRODUCT, "coffee", "beer")
	.subscribeOn(Schedulers.io())
	.observeOn(AndroidSchedulers.mainThread())
	.subscribe(new Action1<GetSkuDetailsResponse>() {
	    @Override
	    public void call(GetSkuDetailsResponse response) {
			if (response.isSuccess()) {
	            response.getList() // list of sku details
	        }
	    }
	}, new Action1<Throwable>() {
	    @Override
	    public void call(Throwable throwable) {

	    }
	});

Get purchases

The Rx version of IInAppBillingService.getPurchases()

ReactiveBilling.getInstance(getContext())
	.getPurchases(PurchaseType.PRODUCT, null)
	.subscribeOn(Schedulers.io())
	.observeOn(AndroidSchedulers.mainThread())
	.subscribe(new Action1<GetPurchasesResponse>() {
	    @Override
	    public void call(GetPurchasesResponse response) {
	        if(response.isSuccess()) {
		        response.getList() // list of purchases
		    }
	    }
	}, new Action1<Throwable>() {
	    @Override
	    public void call(Throwable throwable) {

	    }
	});

Buy product

Buying a product is a little bit different because it's a two step process.

  • Start the purchase flow (show the Play store purchasing dialog)
  • Receive the purchase flow result (receive the result from previous dialog)

Start the purchase flow

The Rx version of IInAppBillingService.getBuyIntent()

In addition, if the request is successful, Reactive Billing will start the purchase flow automatically.

ReactiveBilling.getInstance(getContext())
	.startPurchase(skuDetails.getProductId(), skuDetails.getPurchaseType(), null, null)
	.subscribeOn(Schedulers.io())
	.observeOn(AndroidSchedulers.mainThread())
	.subscribe(new Action1<Response>() {
	    @Override
	    public void call(Response response) {
	        if (response.isSuccess()) {
	            // purchase flow was started successfully, nothing to do here
	        } else {
	            // handle cannot start purchase flow
	        }
	    }
	}, new Action1<Throwable>() {
	    @Override
	    public void call(Throwable throwable) {
	        
	    }
	});

Receive purchase flow results

Because of the Android lifecycle, your activity can be destroyed and recreated while the purchase flow is visible. Therefore the subscriber for the purchase flow events needs to be unsubscribed and subscribed again when the activity is recreated.

Reactive Billing requires to subscribe for the purchase flow events during the initialisation, which is usually represented by the following methods:

  • onCreate() for activities
  • onActivityCreated() for fragments
  • onAttachedToWindow() for views
ReactiveBilling.getInstance(this).purchaseFlow()
    .subscribe(new Action1<PurchaseResponse>() {
        @Override
        public void call(PurchaseResponse response) {
            if (response.isSuccess()) {
                response.getPurchase(); // the purchased product
            }
        }
    });

You would also want to check if the purchase flow was cancelled.

if (response.isSuccess()) {
    response.getPurchase(); // the purchased product
} else if(response.isCancelled()) {
	// purchase flow cancelled
} else {
	response.getResponseCode(); // purchase flow failed, handle the response code
}

Extras

In order to be able to differentiate properly the events receiving in the purchase flow observable, you can provide an "extras" bundle when starting the purchase flow.

Full example

public class BuyActivity extends Activity {

    private Subscription subscription;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        subscription = ReactiveBilling.getInstance(this).purchaseFlow()
            .subscribe(new Action1<PurchaseResponse>() {
                @Override
                public void call(PurchaseResponse response) {
                    // receives the result of the purchase flow
                    if (response.isSuccess()) {
                        response.getPurchase(); // the purchased product
                    } else {
                    	// handle
                    }
                }
            });
    }

    public void onProductClick(String productId) {
        // start the purchase flow
        ReactiveBilling.getInstance(getContext())
			.startPurchase(productId, PurchaseType.PRODUCT, null, null)
			.subscribeOn(Schedulers.io())
			.observeOn(AndroidSchedulers.mainThread())
			.subscribe(new Action1<Response>() {
			    @Override
			    public void call(Response response) {
			        if (response.isSuccess()) {
			            // purchase flow was started successfully, nothing to do here
			        } else {
			            // handle cannot start purchase flow
			        }
			    }
			}, new Action1<Throwable>() {
			    @Override
			    public void call(Throwable throwable) {
			        // handle
			    }
			});
    }

    @Override
    protected void onDestroy() {
        if (subscription != null) {
            subscription.unsubscribe();
            subscription = null;
        }

        super.onDestroy();
    }
}

Consume purchase

The Rx version of IInAppBillingService.consumePurchase()

ReactiveBilling.getInstance(getContext())
    .consumePurchase("purchase token")
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Response>() {
        @Override
        public void call(Response response) {
            if(response.isSuccess()) {
	            // successfully consumed
	        }
        }
    }, new Action1<Throwable>() {
        @Override
        public void call(Throwable throwable) {

        }
    });

Reactive Billing Example app

You can find the Reactive Billing Example app on the play store:
https://play.google.com/store/apps/details?id=com.github.lukaspili.reactivebilling.sample

The source code is located in the current project, under sample/.

Gradle

Reactive Billing is available on Maven Central.
Be sure to grab the aar package.

dependencies {
	compile 'com.github.lukaspili.reactive-billing:reactive-billing:0.2@aar'

	// reactive billing requires the following dependencies
	compile 'io.reactivex:rxjava:1.1.5'
	compile 'com.jakewharton.rxrelay:rxrelay:1.1.0'

	// you would probably want the rx-android dependency as well
	compile 'io.reactivex:rxandroid:1.2.0'
}

Changelog

See the changelog

Acknowledgements

Author

License

Reactive Billing is released under the MIT license. See the LICENSE file for details.

reactive-billing's People

Contributors

lukaspili avatar rharter avatar virusman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reactive-billing's Issues

java.lang.IllegalStateException: Doesn't have any subscription

On my fragment's onCreate() I subscribe to the purchaseFlow() as:

    mSubscription = ReactiveBilling.getInstance(mActivity).purchaseFlow()...

And on the fragment's onDestroy() I unsubscribe it as:

    if (mSubscription != null) {
           mSubscription.unsubscribe();
           mSubscription = null;
    }

This fragment is triggered by a menu button (and dismissed by another close button within the fragment). When the menu and close button is tapped in quick succession, sometimes I will get the crash as below. May I ask if there's any way to prevent it?

                                                            Caused by: java.lang.IllegalStateException: Doesn't have any subscription
                                                               at com.github.lukaspili.reactivebilling.PurchaseFlowService$1.call(PurchaseFlowService.java:38)
                                                               at rx.subscriptions.BooleanSubscription.unsubscribe(BooleanSubscription.java:71)
                                                               at rx.internal.util.SubscriptionList.unsubscribeFromAll(SubscriptionList.java:124)
                                                               at rx.internal.util.SubscriptionList.unsubscribe(SubscriptionList.java:113)
                                                               at rx.Subscriber.unsubscribe(Subscriber.java:98)
                                                               at rx.internal.util.SubscriptionList.unsubscribeFromAll(SubscriptionList.java:124)
                                                               at rx.internal.util.SubscriptionList.unsubscribe(SubscriptionList.java:113)
                                                               at rx.Subscriber.unsubscribe(Subscriber.java:98)
                                                               at rx.internal.util.SubscriptionList.unsubscribeFromAll(SubscriptionList.java:124)
                                                               at rx.internal.util.SubscriptionList.unsubscribe(SubscriptionList.java:113)
                                                               at rx.Subscriber.unsubscribe(Subscriber.java:98)
                                                               at com.example.controller.MenuMainFragment.onDestroy(MenuMainFragment.java:332)
                                                               at android.support.v4.app.Fragment.performDestroy(Fragment.java:2202)
                                                               at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1196)
                                                               at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1252)
                                                               at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1234)
                                                               at android.support.v4.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:2083)
                                                               at android.support.v4.app.FragmentController.dispatchDestroy(FragmentController.java:244)
                                                               at android.support.v4.app.FragmentActivity.onDestroy(FragmentActivity.java:368)
                                                               at android.support.v7.app.AppCompatActivity.onDestroy(AppCompatActivity.java:203)
                                                               at com.example.BaseActivity.onDestroy(BaseActivity.java:131)
                                                               at android.app.Activity.performDestroy(Activity.java:6169)
                                                               at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1141)
                                                               at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3693)
                                                               at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3724) 
                                                               at android.app.ActivityThread.access$1400(ActivityThread.java:151) 
                                                               at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1357) 
                                                               at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                               at android.os.Looper.loop(Looper.java:135) 
                                                               at android.app.ActivityThread.main(ActivityThread.java:5254) 
                                                               at java.lang.reflect.Method.invoke(Native Method) 
                                                               at java.lang.reflect.Method.invoke(Method.java:372) 
                                                               at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) 
                                                               at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698) 

Investigate better solution for threading model

Currently, the android ServiceConnection used to acquire the billing service always returns on the main thread, regardless of the thread that requests the binding.
If the requester thread is not the main thread, I'm using a semaphore to lock that thread, and I'm unlocking it when the billing service is bound.

See BaseObservable.java for the implementation.

Would like to find better solution that doesn't require such hacky thread manipulation.

Library state?

Hi.
Is this library still active?
I am searching for Rx Billing library for my personal project, but looks like some old bugs are still open and new version with subscription support wasn't released.
Should I search for something else?

Need little help?

How can i check if the product is already purchased and how can i determine which product is purchased because i did not find any request code or any identity in Purchase flow.

Will this be updated?

I really cannot ignore my Crashlytics reports anymore. At the time of the purchase, when I need my app to be at it's best I see so many inexplicable errors that I can do nothing about. (I don't have anything new to report, just the same problems that already have their own issues)

This is such an excellent library and I really wish it would handle edge cases better :)

If not, can someone please recommend me another library to save me from Google's cryptic API?

Reason for minsdk of 16?

Hello,

Thanks for putting this together. I'm trying it out now, however I noticed that the defined minSDK is 16. Is there a particular reason for that? If I try to override that or lower it am I going to run into lots of trouble, or is that just the lowest supported version?

Thanks

RxJava 2 support

Not yet sure if there is anything needed to be done here.
I am migrating my project to RxJava2 and will probably stumble on something more concreet along the way.

In general, library should support RxJava2

isBillingSupported blocks scheduler indefinitely

The answer to the question in BaseObservable:

        // freeze the current RX thread until service is connected
        // because bindService() will call the connection callback on the main thread
        // we want to get back on the current RX thread
        ReactiveBillingLogger.log("Acquire semaphore until service is ready (thread %s)", Thread.currentThread().getName());
        semaphore.acquireUninterruptibly();

        // once the semaphore is released
        // it means that the service is connected and available
        //TODO: what happens if the service is never connected?
        deliverBillingService(subscriber);

seems to be "it blocks the scheduler thread forever". Ideally there'd be a way to use a timeout operator here, is there a reason that wouldn't work?

com.android.vending.billing.IInAppBillingService is defined multiple times

I'm trying to use this implementation in my project, but can't compile it with the error message above.

Type com.android.vending.billing.IInAppBillingService is defined multiple times: C:\www\e-Work\test_ed1\app\build\intermediates\project_dex_archive\debug\out\com\android\vending\billing\IInAppBillingService.dex, C:\www\e-Work\test_ed1\app\build\intermediates\external_libs_dex\debug\mergeExtDexDebug\classes.dex

My build.gradle:

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.0'
    }
} 
rootProject.allprojects {
    repositories {
        google()
        jcenter()
    }
}
apply plugin: 'com.android.application'
android {
    compileSdkVersion 28
    buildToolsVersion '29.0.2'
    defaultConfig {
        applicationId "com.myproject"
        minSdkVersion 17
        targetSdkVersion 26
    }
    lintOptions {
        disable 'InvalidPackage'
    } 
}
android {
    useLibrary 'org.apache.http.legacy'
}
dependencies {
    implementation 'com.google.android.gms:play-services-ads:19.5.0'
    compile 'com.github.lukaspili.reactive-billing:reactive-billing:0.2@aar'
    compile 'io.reactivex:rxjava:1.1.5'
//    compile 'com.jakewharton.rxrelay:rxrelay:1.1.0'
    compile 'io.reactivex:rxandroid:1.2.0'
}

Any idea how to fix that? 

Nothing happens on emulators

I tried to check is billing supported and get sku details on emulators, but observable does not terminated at all. It hangs on service binding state, but it does not completes with error or success.
I tried Android Studio emulator with google APIs and genymotion emulator without it. All works fine on real device. What is the problem might be?

Support for Purchase verification?

There's no where in the API to provide the applications public key, nor is there any mechanism to let the caller verify a purchase against said key. This means the application has to implicitly trust any purchase receipt coming from ReactiveBilling, or continue to use portions of googles IabHelper to do the verification.

Would be great to have an additional observable that let you asynchronously verify a purchase.

Possible to test with reserved product ID?

Was testing with android.test.purchased and it was successful, however will get the following crash if test with android.test.canceled or android.test.refunded:

FATAL EXCEPTION: main Process: com.example, PID: 25280 java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1337, result=-1, data=Intent { (has extras) }} to activity {com.example/com.github.lukaspili.reactivebilling.ReactiveBillingShadowActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference at android.app.ActivityThread.deliverResults(ActivityThread.java:3574) at android.app.ActivityThread.handleSendResult(ActivityThread.java:3617) at android.app.ActivityThread.access$1300(ActivityThread.java:151) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1352) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5254) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698) Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference at org.json.JSONTokener.nextCleanInternal(JSONTokener.java:116) at org.json.JSONTokener.nextValue(JSONTokener.java:94) at org.json.JSONObject.<init>(JSONObject.java:156) at org.json.JSONObject.<init>(JSONObject.java:173) at com.github.lukaspili.reactivebilling.parser.PurchaseParser.parse(PurchaseParser.java:19) at com.github.lukaspili.reactivebilling.PurchaseFlowService.onActivityResult(PurchaseFlowService.java:86) at com.github.lukaspili.reactivebilling.ReactiveBillingShadowActivity.onActivityResult(ReactiveBillingShadowActivity.java:87) at android.app.Activity.dispatchActivityResult(Activity.java:6192) at android.app.ActivityThread.deliverResults(ActivityThread.java:3570) at android.app.ActivityThread.handleSendResult(ActivityThread.java:3617)  at android.app.ActivityThread.access$1300(ActivityThread.java:151)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1352)  at android.os.Handler.dispatchMessage(Handler.java:102)  at android.os.Looper.loop(Looper.java:135)  at android.app.ActivityThread.main(ActivityThread.java:5254)  at java.lang.reflect.Method.invoke(Native Method)  at java.lang.reflect.Method.invoke(Method.java:372)  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698) 

Purchase item problem - more devices with same Google acount

Hi,

one purchased item should be purchased on more devices with same Google account, right? "getPurchases(PurchaseType.PRODUCT, null)" returns different results on 2 devices. On first device I get this item as purchased after I bought it here, but on second device it seems not purchased, even after app close and reopen it seems not purchased. Only after app data clear I got it finally purchased here too. Is is library bug? Thanks for help.

Honza

Multiple subscriptions to purchaseFlow

Hi,

I'm new with both Reactive-Billing and RxJava. I'm trying to subscribe multiple subscribers to the purchaseFlow Observable. If i understand correctly, you could accomplish this with share() or publish() but I'm not sure how I would go about implementing this.

The effect I'm trying to accomplish is that multiple (two) views can register to the same purchaseFlow() object independently.

Excuse me if this not an appropriate question here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.