Code Monkey home page Code Monkey logo

androidbillinglibrary's Introduction

Update

In-app Billing v2 API is deprecated and will be shut down in January 2015. This library was developed for v2 a long time ago. If your app is still using this library, please migrate to the v3 API as soon as possible.

The project Android Checkout Library by @serso supports v3 and attemps to provide data compatibility with AndroidBillingLibrary. We haven't verified this so please use it at your own discretion.

Android Billing Library

requestPurchase("com.example.item")

That's how simple it should be to use Android In-app Billing.

And with this library it is.

Android Billing Library implements in-app billing's full specification and offers high-level classes to use it. Transactions are stored in a local obfuscated database which can be easily queried.

Getting Started

  • Get acquainted with the Android In-app Billing documentation.

  • Add Android Billing Library to your project.

  • Open the AndroidManifest.xml of your application and add this permission...

<uses-permission android:name="com.android.vending.BILLING" />

...and this service and receiver inside the application element:

<service android:name="net.robotmedia.billing.BillingService" />
<receiver android:name="net.robotmedia.billing.BillingReceiver">
	<intent-filter>
		<action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
		<action android:name="com.android.vending.billing.RESPONSE_CODE" />
		<action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
	</intent-filter>
</receiver>

That's it!

Usage

Subclassing AbstractBillingActivity

AbstractBillingActivity is an abstract activity that provides default integration with in-app billing (an analogous class for fragments is also provided). It is useful to get acquainted with the library, or for very simple applications that require in-app billing integration in only one activity. For more flexibility use BillingController directly.

When created your AbstractBillingActivity instance will check if in-app billing is supported, followed by a call to onBillingChecked(boolean), which has to be implemented by the subclass.

Additionally, your AbstractBillingActivity subclass will attempt to restore all transactions, only once. This is necessary in case the user has previously installed the app and made purchases. Existing transactions will generate calls to onPurchaseStateChange(String, PurchaseState), which has to be implemented by the subclass.

Starting a purchase is as simple as calling requestPurchase(String). AbstractBillingActivity will start the Google Play intent automatically and onPurchaseStateChange(String, PurchaseState) will be called after the transaction is confirmed.

If you override any of the methods provided by AbstractBillingActivity, make sure to call the superclass implementation.

BillingController

BillingController provides high-level functions to interact with the Billing service and to query an obfuscated local transaction database.

Since most billing functions are asynchronous, BillingController notifies all registered IBillingObserver of the responses.

Additionally, BillingController requires a BillingController.IConfiguration instance from which the public key required to validate signed messages and a salt to obfuscate transactions are obtained. A good place to provide the configuration is in the Application subclass.

Dungeons Redux

Dungeons Redux is a sample app that shows how to use Android Billing Library via BillingController. It is a simplified version of the Dungeons in-app billing example provided by Google.

It should be noted that Dungeons Redux does not intend to be an example of how to use in-app billing in general.

Contact

http://www.twitter.com/robotmedia | http://www.facebook.com/robotmedia | http://www.robotmedia.net

License

Copyright 2011 Robot Media SL (http://www.robotmedia.net)

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

androidbillinglibrary's People

Contributors

braisgabin avatar hpique avatar thunderrabbit 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  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

androidbillinglibrary's Issues

Cancelled order still returned with state PurchaseState.PURCHASED

I created an example project closely modelled on DungeonsRedux. It works just fine and I was able to purchase an item. However after I then cancelled that order in my Google merchant account BillingController.getTransactions() still returns a transaction with the state PurchaseState.PURCHASED

Shouldn't cancelled orders be handled automatically by the library, or do I have to manually handle cancellations in some way?

java.lang.NullPointerException at net.robotmedia.billing.BillingController.startPurchaseIntent

Hi,

I'm seeing a lot of NullPointer Exception:

java.lang.NullPointerException
at net.robotmedia.billing.BillingController.startPurchaseIntent(SourceFile:564)
at net.robotmedia.billing.helper.AbstractBillingObserver.onPurchaseIntent(SourceFile:58)
at net.robotmedia.billing.BillingController.onPurchaseIntent(SourceFile:324)
at net.robotmedia.billing.BillingRequest$RequestPurchase.processOkResponse(SourceFile:128)
at net.robotmedia.billing.BillingRequest.getRequestType(SourceFile:240)
run
at net.robotmedia.billing.BillingService.runRequest(SourceFile:246)
at net.robotmedia.billing.BillingService.runPendingRequests(SourceFile:226)
at net.robotmedia.billing.BillingService.onServiceConnected(SourceFile:147)
at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1064)
at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1081)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:3647)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)

How to properly hide public key?

Documentation says:

To keep your public key safe from malicious users and hackers, do not embed your public key as an entire literal string. Instead, construct the string at runtime from pieces or use bit manipulation (for example, XOR with some other string) to hide the actual key. The key itself is not secret information, but you do not want to make it easy for a hacker or malicious user to replace the public key with another key.

How to do the same with AndroidBillingLibrary?

NPE with IMarketBillingService sendBillingRequest

I have got some stacktraces reported by ACRA. They are very occasional and it seems to be more on google's part but I don't know if it will be nice to catch the error, and log it.

at android.os.Parcel.readException(Parcel.java:1327)
at android.os.Parcel.readException(Parcel.java:1275)
at com.android.vending.billing.IMarketBillingService$Stub$Proxy.sendBillingRequest(SourceFile:100)
at net.robotmedia.billing.request.BillingRequest.getRequestType(SourceFile:82)
                                                 run
at net.robotmedia.billing.BillingService.runPendingRequests(SourceFile:217)
at net.robotmedia.billing.BillingService.onServiceConnected(SourceFile:151)
at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1058)
at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1075)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4123)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)

Verifying application with test items

I am doing the following:

private static final String ANDROID_MARKET_ITEM = "android.test.purchased";
requestPurchase(ANDROID_MARKET_ITEM);

But onPurchaseExecuted is not called. Purchase Dialog is just closed.

What is wrong here?
Also, if after that I check the database:

BillingDB bDB = new BillingDB(getApplicationContext());
Cursor purchases = bDB.queryPurchases();
Log.i(TAG, "Count: " + purchases.getCount());

I see that no records are added to the database.

slow startup with no market and no internet

I have an activity that extends AbstractBillingActivity. Start up was slow slow. And I couldn't pinpoint why. Even when profiling during the slow startup I couldn't pinpoint what was going on. But it was clear the bottle neck was hapenning after onCreate. I have done a postDelayed(..., 50) just before onCreate finishes to time how long it is and so I can speed things up.

Things speed up after I enabled Wifi And setup android market. I'm not fully sure it's an android billing issue but I guess since I see messages regarding it in the log, and I got a speed up after setting up the android market.

Here are adb log with my stuff removed and hidden away (Except whats important)

10-03 17:15:30.390: DEBUG/NailActivity(2026): time for NailActivity **first init 1.095** this is my code for onCreate() and here I call postDelayed(..., 50)
10-03 17:15:30.390: DEBUG/NailActivity(2026): time for **onResume: 0.0** on Resume happens fast
10-03 17:15:30.430: DEBUG/dalvikvm(2026): GC_CONCURRENT freed 672K, 19% free 8424K/10375K, paused 2ms+3ms
10-03 17:15:30.450: DEBUG/MyView(2026): setUpScale in onSizeChanged
10-03 17:15:35.480: ERROR/vending(329): [8] VendingApplication.getCurrentAccount(): Countdown latch timeout expired when getting current account!
10-03 17:15:37.590: DEBUG/FlurryAgent(2026): generating report
10-03 17:15:37.590: DEBUG/FlurryAgent(2026): Sending report to: http://data.flurry.com/aap.do
10-03 17:15:37.620: DEBUG/FlurryAgent(2026): Sending report exception: Unable to resolve host "data.flurry.com": No address associated with hostname
10-03 17:15:39.290: WARN/ActivityManager(146): Launch timeout has expired, giving up wake lock!
10-03 17:15:39.290: WARN/ActivityManager(146): Activity idle timeout for ActivityRecord{414f54a8 com.mycompany.myapp/.NailActivity}
10-03 17:15:40.470: ERROR/vending(329): [8] InAppBillingState.isEnabled(): IAB latch timeout expired while waiting for metadata response.
10-03 17:15:40.480: WARN/CheckBillingSupported(2026): Error with response code RESULT_BILLING_UNAVAILABLE
10-03 17:15:42.300: INFO/InputDispatcher(146): Application is not responding: Window{40ca40b8 com.mycompany.myapp/com.mycompany.myapp.NailActivity paused=false}.  5005.7ms since event, 5005.4ms since wait started
10-03 17:15:42.300: INFO/WindowManager(146): Input event dispatching timed out sending to com.mycompany.myapp/com.mycompany.myapp.NailActivity
10-03 17:15:42.330: INFO/Process(146): Sending signal. PID: 2026 SIG: 3
10-03 17:15:42.330: INFO/dalvikvm(2026): threadid=4: reacting to signal 3
10-03 17:15:42.340: INFO/dalvikvm(2026): Wrote stack traces to '/data/anr/traces.txt'
**a bunch of stack traces lines like last 3 above repating**
10-03 17:15:42.610: DEBUG/dalvikvm(146): GC_EXPLICIT freed 3064K, 43% free 16217K/28295K, paused 3ms+4ms
10-03 17:15:43.190: ERROR/ActivityManager(146): ANR in com.mycompany.myapp (com.mycompany.myapp/.NailActivity)
10-03 17:15:43.190: ERROR/ActivityManager(146): Reason: keyDispatchingTimedOut
10-03 17:15:43.190: ERROR/ActivityManager(146): Load: 0.25 / 0.1 / 0.06
10-03 17:15:43.190: ERROR/ActivityManager(146): CPU usage from 11490ms to 0ms ago:
10-03 17:15:43.190: ERROR/ActivityManager(146):   2.6% 146/system_server: 1.5% user + 1% kernel / faults: 13 minor
10-03 17:15:43.190: ERROR/ActivityManager(146):   0.5% 2026/com.mycompany.myapp: 0.4% user + 0% kernel / faults: 46 minor
10-03 17:15:43.190: ERROR/ActivityManager(146):   0.3% 85/surfaceflinger: 0.1% user + 0.1% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):   0% 991/kworker/u:1: 0% user + 0% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):   0.3% 1333/kworker/u:2: 0% user + 0.3% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):   0.1% 3/ksoftirqd/0: 0% user + 0.1% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):   0.1% 230/com.android.launcher: 0.1% user + 0% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):   0.1% 2053/kworker/0:3: 0.1% user + 0% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):   0% 83/netd: 0% user + 0% kernel / faults: 63 minor
10-03 17:15:43.190: ERROR/ActivityManager(146):   0% 101/adbd: 0% user + 0% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):   0% 127/irq/182-3d: 0% user + 0% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):   0% 329/com.android.vending: 0% user + 0% kernel / faults: 6 minor
10-03 17:15:43.190: ERROR/ActivityManager(146):   0% 835/logcat: 0% user + 0% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):   0% 1218/kworker/1:0: 0% user + 0% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146): 1.5% TOTAL: 0.8% user + 0.7% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146): CPU usage from 319ms to 845ms later:
10-03 17:15:43.190: ERROR/ActivityManager(146):   7.4% 146/system_server: 0% user + 7.4% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):     7.4% 171/InputDispatcher: 0% user + 7.4% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146):     1.8% 167/PowerManagerSer: 0% user + 1.8% kernel
10-03 17:15:43.190: ERROR/ActivityManager(146): 2.5% TOTAL: 0% user + 2.5% kernel
10-03 17:15:44.330: DEBUG/dalvikvm(675): GC_EXPLICIT freed 16K, 5% free 6305K/6595K, paused 4ms+2ms
10-03 17:15:45.500: ERROR/vending(329): [41] VendingApplication.getCurrentAccount(): Countdown latch timeout expired when getting current account!
10-03 17:15:49.340: DEBUG/dalvikvm(550): GC_EXPLICIT freed 32K, 7% free 7085K/7559K, paused 6ms+2ms
10-03 17:15:50.490: ERROR/vending(329): [41] **InAppBillingState.isEnabled(): IAB latch timeout expired **while waiting for metadata response. _Now I my code begins to execute and all is normal__
10-03 17:15:51.940: DEBUG/NailActivity(2026): time for NailActivity **after init 1.362** this is how long postDelayed function took (my code)
10-03 17:15:51.940: DEBUG/NailActivity(2026): time for NailActivity **total init 22.645** this is the total init time

Usage clarifications to be added

It is not quite clear where to input public key and in which format. Shoud I return that as result in getPublicKey()?
Also not quite clear how 'salt' related functions should be used (and what they are for).

Not quite clear what is automated with regards to the database - what is written automatically there?

market crashes on restoreTransactions()

on first run i call:

BillingController.restoreTransactions(mActivity);

transactions are restored correctly but a popup appears sayng that the market have just crashed.
from logcat:

java.lang.IllegalArgumentException: Type mismatch type:null tag:1
 at com.google.common.io.protocol.ProtoBuf.assertTypeMatch(ProtoBuf.java:897)
 at com.google.common.io.protocol.ProtoBuf.insertObject(ProtoBuf.java:1022)
 at com.google.common.io.protocol.ProtoBuf.insertString(ProtoBuf.java:827)
 at com.google.common.io.protocol.ProtoBuf.addString(ProtoBuf.java:173)
 at com.android.vending.model.AckNotificationsRequest.addNotificationId(AckNotificationsRequest.java:40)
 at com.android.vending.util.AlarmService$AsynchAckNotifications.doRequest(AlarmService.java:274)
 at com.android.vending.billing.MarketBillingService$AsynchInAppAckNotifications.doRequest(MarketBillingService.java:640)
 at com.android.vending.AsynchRequestRunner$RequestRunnable.run(AsynchRequestRunner.java:37)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
 at java.lang.Thread.run(Thread.java:1019)
 at com.android.vending.AsynchRequestRunner$VendingThreadFactory$VendingFactoryThread.run(AsynchRequestRunner.java:118)

Failed transaction keeps replaying over and over

I made the mistake of not using my public key when trying to execute a purchase of a non-managed, non-test product id. Obviously this failed; no big deal - this is the expected behavior. I have since fixed my code to use my public key. Now, test product-id's purchase just fine. But each time I purchase a test product id, I get TWO onPurchasedStateChanged. One is the successful transaction for the intended purchase, but the second is a CANCELLED state for the previously failed non-mananged non-test product ID that I tried to buy without using my public key.

I have since cleared data, and uninstalled my app, as well as clearing data on the Market app. Upon reinstalling my app, and re-agreeing to the Market app showing me its TOS, I start my app, do a test purchase and low and behold that failed buy is STILL generating CANCELLED states for onPurchasedStateChanged. I have rebooted the phone and cleared data until Im about to scream. Where is the bad purchase attempt coming from? I KNOW my app is not trying to do a buy with the offending product id.

Please help!

Method to get transaction of the last purchase of an item

It will be nice to have a method to obtain the last transaction of a purchased item.
That can be util to look for purchaseTime and implement time limited purchases.

Proposed patch:
diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java
index e3c315f..a9da843 100644
--- a/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java
+++ b/AndroidBillingLibrary/src/net/robotmedia/billing/BillingController.java
@@ -191,6 +191,26 @@ public class BillingController {
}
return count;
}

  • /**
  • * Returns transaction information of the last purchase of an item.
  • * Please take note that this method doesn't take in account cancelations or refunds.
  • * @param context
  • * @param itemId
  • * @return Last purchase ransaction of item or null if not found
  • */
  • public static Transaction getLastPurchase(Context context,String itemId) {
  •   Transaction lastTransaction=null;
    
  •   List<Transaction> transactions=getTransactions(context,itemId);
    
  •   for(Transaction transaction:transactions) {
    
  •       if(lastTransaction==null || transaction.purchaseTime>lastTransaction.purchaseTime) {
    
  •           if(transaction.purchaseState==PurchaseState.PURCHASED) {
    
  •               lastTransaction=transaction;
    
  •           }  
    
  •       }  
    
  •   }
    
  •   return lastTransaction;
    
  • }

/**

* Requests purchase information for the specified notification. Immediately

1.7.6.msysgit.0

Purchase States

While testing the app, I see that purchase of android.test.purchased is reflected in the database. But purchase status there is 0. Hence isPurchased returns false.

I've checked possible states

    public enum PurchaseState {
        // Responses to requestPurchase or restoreTransactions.
        PURCHASED,    // 0: The charge failed on the server.
        CANCELLED,   // 1: User was charged for the order.
        REFUNDED;    // 2: User received a refund for the order.

Is everything correct here?

DUPLICATE PURCHASE Handling IN_APP_NOTIFY messages

Hi,
I really appreciate your efforts and I am very thankful for making this module open source. Well I have observed some issues. Please read reference first.

URL: http://developer.android.com/guide/market/billing/billing_overview.html
Handling multiple IN_APP_NOTIFY messages

" Sometimes, however, Android Market may send repeated IN_APP_NOTIFY intents for a PURCHASE_STATE_CHANGED message even though your application has sent a CONFIRM_NOTIFICATIONS message. . In this case, Android Market might not receive your CONFIRM_NOTIFICATIONS message and it could send multiple IN_APP_NOTIFY messages until it receives acknowledgement that you received the transaction message.

Therefore, your application must be able to recognize that the subsequent IN_APP_NOTIFY messages are for a previously processed transaction. You can do this by checking the orderID that's contained in the JSON string because every transaction has a unique orderId. "

  1. You should only call onPurchaseStateChanged when order id is unique. as you are maintaining a db of purchases.
  2. write a helper method in TransactionManager public static boolean isUnique(int orderId) it will look into local db that tells weather or is duplicate or not.
  3. it should also pass order id in public void onPurchaseStateChanged(String itemId, PurchaseState state, int orderId) {

What happening is due to internet failure it is calling onPurchaseState many times.

Regards,
drjava72

BillingController.setDebug()

it could be useful to have the ability to turn BillingController debug on/off from outside the library.

it would also help to have BillingController.onPurchaseStateChanged() signedData logged.

thank you.

Basic example needed

I see a nice sample app for the BillingController method (DungeonsRedux), but no sample app for the AbstractBillingLibrary method. Does such an example exist?

I have subclassed the AbstractBillingActivity in my own activity, but am getting NPE when calling checkBillingSupported(), for example.

It would be really valuable to see a very basic class showing the subclassing and subsequent calls made, etc.

Thanks,
-Jason

The buy button is greyed

I open "Dungeons Redux" and, as expected, the "buy" button can be clicked.
But, if i click the back button on my phone, and then reopen "Dungeons Redux", then the "buy" button is greyed.

It seems that "onBillingChecked" on "Dungeons.java" is not called the second time.
Am I missing something?

in app purchase android Compatibility. java InvocationTargetException issues

I am getting this issues when checkout activity is open and back to the co

Compatibility.java file

java.lang.reflect.InvocationTargetException

Please tell me how i can handle this ...

I have 3 activity
1 Activity is the store e name
2 activity is list of buy product lost
3 Checkout out screen ..

2 activity to 3 activity is working fine but when i came back from 2 to 1 and again goes to 3 activity (checkout activity ) after successfully bought it show following exception please tell me the solution for that..

java.lang.reflect.InvocationTargetException
09-27 10:13:50.155: ERROR/Exception(20677): at android.app.Activity.startIntentSender(Activity.java:2944)
09-27 10:13:50.155: ERROR/Exception(20677): at java.lang.reflect.Method.invokeNative(Native Method)
09-27 10:13:50.155: ERROR/Exception(20677): at java.lang.reflect.Method.invoke(Method.java:521)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.Compatibility.startIntentSender(Compatibility.java:77)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.inapp.BillingController.startPurchaseIntent(BillingController.java:559)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.helper.AbstractBillingObserver.onPurchaseIntent(AbstractBillingObserver.java:58)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.inapp.BillingController.onPurchaseIntent(BillingController.java:315)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.inapp.BillingRequest$RequestPurchase.processOkResponse(BillingRequest.java:128)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.inapp.BillingRequest.run(BillingRequest.java:246)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.inapp.BillingService.runRequest(BillingService.java:223)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.inapp.BillingService.runRequestOrQueue(BillingService.java:241)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.inapp.BillingService.requestPurchase(BillingService.java:200)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.inapp.BillingService.handleCommand(BillingService.java:182)
09-27 10:13:50.155: ERROR/Exception(20677): at com.test.device.inapp.BillingService.onStartCommand(BillingService.java:168)
09-27 10:13:50.155: ERROR/Exception(20677): at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3053)
09-27 10:13:50.155: ERROR/Exception(20677): at android.app.ActivityThread.access$3600(ActivityThread.java:125)
09-27 10:13:50.155: ERROR/Exception(20677): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2096)

AbstractBillingFragment

Provide a class similar to AbstractBillingActivity for fragments (requested by roflharrison via email).

Configuration is null

Hi,

First of all thanks so much for your great work.

I'm playing with the lib for few days now and i reached an issue since this morning i can't figure out how does it happen.

The code seems to work and since yesterday the Billingcotroller replies me to set the public key. I already set up the public key and as i said the code worked for few days.

By inspecting the code (BillingController), i found that the configuration is always null that's why i got this warning. But i can't figure why...

Any help is welcome.

here is the simple code i use:

    public class InAppBilling extends AbstractBillingActivity {

    private boolean once;       

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {       
        super.onCreate(savedInstanceState);     

        final Bundle msg = this.getIntent().getExtras();            

        //InAppBilling.this.requestPurchase(msg.getString("item"));

        //test
        this.requestPurchase("android.test.purchased");

        once  = true;        
    }

    // billing

    public void showInfo(String msg)
    {
        AlertDialog alert = new AlertDialog.Builder(this).create();
        alert.setTitle("Information");
        alert.setMessage(msg);
        alert.setIcon(R.drawable.icon);
        alert.setButton("Ok", new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int which) {
                 InAppBilling.this.onBackPressed();
            } });
        alert.show();
    }

    @Override
    protected void onResume() {
       super.onResume();   
       if(once)
            {once=!once;}
        else
            {onBackPressed();}
   }

    @Override
    public void onBillingChecked(boolean supported) {
        if(supported)
            return;
        showInfo("Sorry In-App billing is not available from your phone, try our product later.");
    }
    @Override
    public void onPurchaseCancelled(String itemId) {
        showInfo("You sucessfully cancelled the item " + itemId);       
    }
    @Override
    public void onPurchaseExecuted(String itemId) {         
        showInfo("Thank you. You sucessfully purchased the item " + itemId);
        ModelList.unlocked(this,itemId);
    }       

    @Override
    public void onPurchaseRefunded(String itemId) {
        showInfo("You sucessfully refunded the item " + itemId);
    }
    @Override
    public byte[] getObfuscationSalt() {
        // TODO Auto-generated method stub          
        return null;
    }
    @Override
    public String getPublicKey() {
        // TODO Auto-generated method stub
        return "myKEYwasHERE";
    }       
    }

Differentiate between purchases and transactions

There's isn't a clear distinction between transactions and purchases (a transaction with purchased state).

While this problem is algo present in Google's sample code and documentation, that doesn't mean that the library should propagate it.

isPurchased Javadoc is vague

The Javadoc of BillingController.isPurchased does not clarify that it does not consider refunds and cancellations.

Is restoreTransaction() should be called more than once ?

I'm trying your library with the Dungeons Redux sample. At some point we can find something like that :

    if(!mBillingObserver.isTransactionsRestored())
        BillingController.restoreTransactions(this);
        ....

BillingController.restoreTransactions(this) is called each time the app is launching and not only the first time. Is that because there is nothing to restore or should i manually write some shared preference ?

BillingDB columns should be public

Should not BillingDB columns be public?
Since they are not public now, it is impossible to extract particular value from the database:

    BillingDB bDB = new BillingDB(getApplicationContext());
    Cursor purchases = bDB.queryPurchases();
    if (purchases.getCount()!=0) {
        purchases.getString(purchases.getColumnIndexOrThrow(BillingDB.COLUMN_PRODUCT_ID));
    } else {            
        requestPurchase(ANDROID_MARKET_ITEM);
    }
    bDB.close();

Subclass PreferenceActivity

There should be some other way to use AndroidBillingLibrary instead of

extends AbstractBillingActivity

used currently. I had to modify AbstractBillingActivity to use this library with PreferenceActivity.

RemoteException handling not supported

Hi,

I have a test-case where I simulate that the market is stopped in an purchase:

  1. Lunch a test app with a buy button. At this point I am connected with help of the android billing library
  2. I do a force stop on the "Market" application from the "Manage applications" settings
  3. This will generate a remote exception

The current implementation in BillingService.java looks like this:

private void runRequest(BillingRequest request) {
    try {
        final long requestId = request.run(mService);
        BillingController.onRequestSent(requestId, request);
    } catch (RemoteException e) {
        Log.w(this.getClass().getSimpleName(), "Remote billing service crashed");
        // TODO: Retry?
    }
}

So the question here is how to handle: "//TODO: Retry?"

Here is my suggestion in code, don't know if anyone else have a better fix or suggestion how to handle this?

Suggested improvements:

BillingService.java:

public void onServiceDisconnected(ComponentName name) {

    // Genxbit customization to handle the case when we are disconnected.

    // Ensure we're not leaking Android Market billing service
    if (mService != null) {
        try {
            unbindService(this);
        } catch (IllegalArgumentException e) {
            // This might happen if the service was disconnected
        }
    }

    mService = null;
}


private void runRequest(BillingRequest request) {
    try {
        final long requestId = request.run(mService);
        BillingController.onRequestSent(requestId, request);
    } catch (RemoteException e) {
        Log.w(this.getClass().getSimpleName(), "Remote billing service crashed");

        // Genxbit customization

        onServiceDisconnected(null);
        request.onResponseCode(ResponseCode.RESULT_ERROR);
    }
}


// Genxbit customization

public static boolean hasService() {
    return mService != null;
}

BillingController.java

public static BillingStatus checkBillingSupported(Context context) {

    // Genxbit customization

    if (status == BillingStatus.UNKNOWN || !BillingService.hasService()) {
        BillingService.checkBillingSupported(context);
    }
    return status;
}

So then for the retry this can be up to the actual individual implementation to handle in my case I use the the "onRequestPurchaseResponse" callback and in case for ResponseCode.RESULT_ERROR I just perform a BillingController.checkBillingSupported(applicationContext).

Unable to start receiver net.robotmedia.billing.BillingReceiver

One of my user got the following error:

java.lang.RuntimeException: Unable to start receiver net.robotmedia.billing.BillingReceiver: java.lang.ClassCastException: android.preference.Preference
at android.app.ActivityThread.handleReceiver(ActivityThread.java:1805)
at android.app.ActivityThread.access$2400(ActivityThread.java:117)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:981)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3683)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.ClassCastException: android.preference.Preference
at spb.bridges.Preferences.onPurchaseExecuted(Preferences.java:89)
at net.robotmedia.billing.BillingController.notifyPurchaseStateChange(BillingController.java:242)
at net.robotmedia.billing.BillingController.onPurchaseStateChanged(BillingController.java:374)
at net.robotmedia.billing.BillingReceiver.purchaseStateChanged(BillingReceiver.java:57)
at net.robotmedia.billing.BillingReceiver.onReceive(BillingReceiver.java:44)
at android.app.ActivityThread.handleReceiver(ActivityThread.java:1794)
... 10 more

What can be the reason?

Item Unavailable for my own in-app products

Hi,

I have followed through with your DungeonRedux example and set my own app in exactly the same way, however, whenever I go to try and purchase a "real" in-app product, I simply get "Item Unavailable - The item that you requested is not available for purchase".

The android.test.purchased situation seems to work fine, just not my own products.

I had wondered whether my app had to be published (despite the Google documentation suggesting otherwise), but having temporarily published my own test copy of DungeonRedux, I can now see that doesn't help either. For the record, neither "sword_001" & "potion_001" work either. Both return the same message as my own.

Any thoughts gratefully received

Regards

DC

Debug facility to log BillingReceiver messages

I think BillingReceiver should log immediatly received messages when DEBUG is activated.
Something like Log.d(BillingReceiver.class.getSimpleName(),"Received billing message of type "+action+":"+intent.toString());

I found that information very important, at least while InApp billing and the library are not very mature, because I had some problems with lost purchased notifications and it's not absolutely clear where is the problem.

Sample app - verification needed

Since I am not able to test the functionality with reserved items and Google doesn't allow me to test that end-to-end with test account (got warning message from them), could you please help me to verify my approach & code below.

The purpose is quite simple - user buys Ad-free version of application in preferences.
So, I did the following:

MainActivity.java:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // remove ad, if user paid for the application
        Boolean purchased = PurchaseManager.isPurchased(getApplicationContext(), Preferences.ANDROID_MARKET_ITEM);
        if (purchased) {
            // here I remove the ad
        }

Preferences.java:

public class Preferences extends AbstractBillingActivity {
public static final String ANDROID_MARKET_ITEM = "my_managed_item_id"; 
private boolean billingSupported = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Preference buyPref = (Preference) findPreference("pref_billing_buy");
        buyPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
            public boolean onPreferenceClick(Preference preference) {   
                if (checkBillingSupported() != BillingStatus.SUPPORTED) {
                    showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
                } else {
                    Boolean purchased = PurchaseManager.isPurchased(getApplicationContext(), ANDROID_MARKET_ITEM);
                    if (!purchased) {
                        // try to restore transactions first
                        restoreTransactions();
                        // not exactly correct approach is below - we will know that user bought the item if trx is restored
                                                // later, not when next line is executed
                        purchased = PurchaseManager.isPurchased(getApplicationContext(), ANDROID_MARKET_ITEM);
                        if (!purchased) {
                            requestPurchase(ANDROID_MARKET_ITEM);
                        }
                    }
                }
                return true;
            }
        });

    @Override
    public void onBillingChecked(boolean supported) {
        billingSupported = supported;       
    }

        // do nothing except logging below - storage in the DB handled automatically by the lib
    @Override
    public void onPurchaseCancelled(String itemId) {
        Log.i(TAG, "Transaction has been refunded: "+itemId);
    }

    @Override
    public void onPurchaseExecuted(String itemId) {     
        Log.i(TAG, "User bought ad-free version: "+itemId);
    }

    @Override
    public void onPurchaseRefunded(String itemId) {
        Log.i(TAG, "Transaction has been refunded: "+itemId);       
    }

    @Override
    public byte[] getObfuscationSalt() {
        return new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
    }

    @Override
    public String getPublicKey() {
        return "my_key_is_here";
    }

ANR crash when trying to purchase something without having market configured

I just did a clean reset of my device. So I didn't setup my android market account yet.

I tried a purchase then, and i got this error:

E/AndroidRuntime( 1030): java.lang.RuntimeException: Unable to start service net.robotmedia.billing.BillingService@2b031a48 with Intent { act=com.ophidian.test.droid.RE
QUEST_PURCHASE cmp=com.ophidian.test.droid/net.robotmedia.billing.BillingService (has extras) }: java.lang.NullPointerException
E/AndroidRuntime( 1030): at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2078)
E/AndroidRuntime( 1030): at android.app.ActivityThread.access$2800(ActivityThread.java:124)
E/AndroidRuntime( 1030): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1010)
E/AndroidRuntime( 1030): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime( 1030): at android.os.Looper.loop(Looper.java:130)
E/AndroidRuntime( 1030): at android.app.ActivityThread.main(ActivityThread.java:3715)
E/AndroidRuntime( 1030): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 1030): at java.lang.reflect.Method.invoke(Method.java:507)
E/AndroidRuntime( 1030): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
E/AndroidRuntime( 1030): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
E/AndroidRuntime( 1030): at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime( 1030): Caused by: java.lang.NullPointerException
E/AndroidRuntime( 1030): at android.os.Parcel.readException(Parcel.java:1328)
E/AndroidRuntime( 1030): at android.os.Parcel.readException(Parcel.java:1276)
E/AndroidRuntime( 1030): at com.android.vending.billing.IMarketBillingService$Stub$Proxy.sendBillingRequest(IMarketBillingService.java:100)
E/AndroidRuntime( 1030): at net.robotmedia.billing.request.BillingRequest.run(BillingRequest.java:79)
E/AndroidRuntime( 1030): at net.robotmedia.billing.BillingService.runRequest(BillingService.java:223)
E/AndroidRuntime( 1030): at net.robotmedia.billing.BillingService.runRequestOrQueue(BillingService.java:237)
E/AndroidRuntime( 1030): at net.robotmedia.billing.BillingService.requestPurchase(BillingService.java:200)
E/AndroidRuntime( 1030): at net.robotmedia.billing.BillingService.handleCommand(BillingService.java:182)
E/AndroidRuntime( 1030): at net.robotmedia.billing.BillingService.onStartCommand(BillingService.java:168)
E/AndroidRuntime( 1030): at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2065)
E/AndroidRuntime( 1030): ... 10 more
W/ActivityManager( 151): Force finishing activity com.ophidian.test.droid/.billing.VaultActivity

Service doesn't stop itself

I'm just looking through the code now, but I can't see where the service stops itself when the work is done (looking for a stopSelf()). After I finish() the app the BillingService continues to run. How are you stopping the service when using this library?

Many thanks,
Richard

ANR stack trace

Some times i get a force close with the stack trace below, then waiting a short while the error goes away.Any ideas?

java.lang.RuntimeException: Unable to start service net.robotmedia.billing.BillingService@46265ad8 with Intent { act=com.123.REQUEST_PURCHASE cmp=com.123/net.robotmedia.billing.BillingService (has extras) }: java.lang.NullPointerException
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3282)
at android.app.ActivityThread.access$3600(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2211)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:144)
at android.app.ActivityThread.main(ActivityThread.java:4937)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at android.os.Parcel.readException(Parcel.java:1253)
at android.os.Parcel.readException(Parcel.java:1235)
at com.android.vending.billing.IMarketBillingService$Stub$Proxy.sendBillingRequest(IMarketBillingService.java:100)
at net.robotmedia.billing.request.BillingRequest.run(BillingRequest.java:79)
at net.robotmedia.billing.BillingService.runRequest(BillingService.java:224)
at net.robotmedia.billing.BillingService.runRequestOrQueue(BillingService.java:238)
at net.robotmedia.billing.BillingService.requestPurchase(BillingService.java:201)
at net.robotmedia.billing.BillingService.handleCommand(BillingService.java:183)
at net.robotmedia.billing.BillingService.onStartCommand(BillingService.java:169)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3267)
... 10 more/

Primary Key of BillingDB is wrong

That's an important one as it can leave the database in a inconsistent logical state.

With current PK if you buy, cancel and buy another time the purchaseCount is 0 because as the pk is only the order IF the first purchase is lot (overriden with the cancel of the order).

The PK should be at least order_id and column_state. I think that product_id should be and I'm unsure about purchasing_time.
I also think that Transaction equals should replicate the same "bussines key" we choose in database.

Suggested patch:

From 100eabab7e107b5915f49fd841a09d1fa8bc4b62 Mon Sep 17 00:00:00 2001
From: lujop <[email protected]>
Date: Sat, 23 Jul 2011 16:39:11 +0200
Subject: [PATCH 3/3] Change the PK of table to don't override purchases with
 cancelations or refunds

---
 .../net/robotmedia/billing/model/BillingDB.java    |    6 ++++--
 1 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/AndroidBillingLibrary/src/net/robotmedia/billing/model/BillingDB.java b/AndroidBillingLibrary/src/net/robotmedia/billing/model/BillingDB.java
index a56a80b..71779de 100644
--- a/AndroidBillingLibrary/src/net/robotmedia/billing/model/BillingDB.java
+++ b/AndroidBillingLibrary/src/net/robotmedia/billing/model/BillingDB.java
@@ -97,11 +97,13 @@ public class BillingDB {

         private void createTransactionsTable(SQLiteDatabase db) {
             db.execSQL("CREATE TABLE " + TABLE_TRANSACTIONS + "(" +
-                   COLUMN__ID + " TEXT PRIMARY KEY, " +
+                   COLUMN__ID + " TEXT, " +
                    COLUMN_PRODUCT_ID + " INTEGER, " +
                    COLUMN_STATE + " TEXT, " +
                    COLUMN_PURCHASE_TIME + " TEXT, " +
-                   COLUMN_DEVELOPER_PAYLOAD + " INTEGER)");
+                   COLUMN_DEVELOPER_PAYLOAD + " INTEGER, " +
+                   "PRIMARY KEY("+COLUMN__ID+", "+COLUMN_PRODUCT_ID+", "+COLUMN_STATE+") "+
+                   ")");
         }

        @Override
-- 
1.7.6.msysgit.0

Market crashes doing any action in Dungeons example

I joined the code that is available in the github with no modifications int the code and when I' trying to do any action (purchase, cancel, refund, etc...) the market opens and inmediatly crashes why is that happening?
Thank you for your time

onTransactionsRestored

Add an onTransactionsRestored event to IBillingObserver.

Currently the observer receives calls for each restored purchase, but nothing that indicates that all restored purchases has been processed.

BillingController.isPurchased always returns false

I have two activities - MainActivity extends ListActivity and Preferences extends AbstractBillingActivity.

If BillingController.isPurchased is called at MainActivity, then false is always returned. But if it is called at Preferences, then it returns correct value (true).
Why it happens so?

BillingController.getTransactions sporadically returns null items

I have an activity closely modelled after the DungeonsRedux example. When I purchase an item BillingController.getTransactions returns proper results.

However, when I restart the Activity (causing onCreate to be called again), BillingController.getTransactions returns items with Transaction.productId == null. Restarting the Activity again the items that previously had Transaction.productId = null will now instead have an Transaction.productId that is obfuscated. Some items always have a correct Transaction.productId (ie. not null and not obfuscated).

What would cause BillingController.getTransactions to return transactions with Transaction.productId == null or still obfuscated mixed in with proper ones?

stored testing productIds

testing products id are stored in the local database as they are ("android.test.purchased", "android.test.refunded"...) so that every transaction is indipendent from previous ones.

my proposal is something like the following.

in BillingDB:

private String fixPurchaseProductId(String purchaseProductId) {
    if (purchaseProductId.startsWith("android.test.")) return "android.test";
    return purchaseProductId;
}

and then:

public void insert(Purchase purchase) {
    //...
    values.put(COLUMN_PRODUCT_ID, fixPurchaseProductId(purchase.productId));
    //...
}

//...

public Cursor queryPurchases(String productId, PurchaseState state) {
    return mDb.query(TABLE_PURCHASES, TABLE_PURCHASES_COLUMNS, COLUMN_PRODUCT_ID + " = ? AND " + COLUMN_STATE + " = ?", 
        new String[] {fixPurchaseProductId(productId), String.valueOf(state.ordinal())}, null, null, null);
}

let me know, thanks.

market crashes on restoreTransactions

Hi,

I don't think that the closed issue#37 is correctly fixed, looking at the code I think it might be something wrong in the case for manual confirmations:

What I have seen from my testing, restore transactions do not always provide a notification id. Hence, it should not internally in the BillingController be added to the manual confirmation array at all?

So a suggestion instead of this:

public static void onSignatureValidateResult(Context context, String signedData, String signature, boolean validationSuccessful) {
.

.


for (Transaction p : purchases) {

if (p.notificationId != null && automaticConfirmations.contains(p.productId)) {

confirmations.add(p.notificationId);

} else {

// TODO: Discriminate between purchases, cancellations and
//
refunds.
addManualConfirmation(p.productId, p.notificationId);

}

storeTransaction(context, p);

notifyPurchaseStateChange(p.productId, p.purchaseState);

}

.

.

It could be something like this and in the notifyPurchaseStateChange callback the BillingController.confirmNotifications call would not matter in this case as it will not find the item in the manual confirmation list:

public static void onSignatureValidateResult(Context context, String signedData, String signature, boolean validationSuccessful) {
.

.
for (Transaction p : purchases) {
if (p.notificationId != null) {
if (automaticConfirmations.contains(p.productId)) {
confirmations.add(p.notificationId);
} else {
// TODO: Discriminate between purchases, cancellations and
// refunds.
addManualConfirmation(p.productId, p.notificationId);
}
}

    storeTransaction(context, p);
    notifyPurchaseStateChange(p.productId, p.purchaseState);
}

.
.
.

Autoconfirmations for refunds and cancelations

I think that will be nice to have also the possibility to configure autoconfirmations for refunds and cancelations.

Because if you delegate completely the state of the purchases to the BillingDB and you haven't any state that can be lost if the confirmation is automanaged then manual confirmation isn't needed if it can be automated.

Change license

The library is currently licensed under LGPL, which has caused some concerns to developers who want to use it un proprietary software. Change the license to Apache License 2.0.

Handling response codes (IN_APP_NOTIFY)

How are you handling response codes after a purchase is made. BillingController is logging them in debug mode, but no callback on IBillingObserver is made?

Support custom Verification/Security

The only issue I can see so far is that there is no easy way to allow custom verification/security classes and methods.

For example, we do all of our verification on a server, so we need to use a custom verify method.

If there is a way this can be done, please let me know.

Edit: To help clarify why I am asking for this, if you check the Android Documentation they request that you do verification on a server due to security risks.

Adam

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.