I seem to have trouble with the In app billing for android
I followed this guideAndroid Google Play In-App Billing which was really helpful in setting it up but i don't understand it exactly
what id like to know is
can i start the service by the user clicking a certain button and how do i start the service , also is there a way to set it if the user has not bought the content that i could maybe show a dialog box and destroy it when the user has bout the app
any advice or help will be much appreciated
here is my code:
package com.IrishSign.app;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.IrishSign.app.BillingService.RequestPurchase;
import com.IrishSign.app.BillingService.RestoreTransactions;
import com.IrishSign.app.Consts.PurchaseState;
import com.IrishSign.app.Consts.ResponseCode;
public class IrishSignAppActivity extends Activity {
/**
* The SharedPreferences key for recording whether we initialized the
* database. If false, then we perform a RestoreTransactions request
* to get all the purchases for this user.
*/
private static final String DB_INITIALIZED = "db_initialized";
private AppPurchaseObserver appPurchaseObserver;
private Handler handler;
private BillingService billingService;
private PurchaseDatabase purchaseDatabase;
private Set<String> ownedItems = new HashSet<String>();
/**
* The developer payload that is sent with subsequent
* purchase requests.
*/
private String payloadContents = null;
/**
* Each product in the catalog is either MANAGED or UNMANAGED. MANAGED
* means that the product can be purchased only once per user (such as a new
* level in a game). The purchase is remembered by Android Market and
* can be restored if this application is uninstalled and then
* re-installed. UNMANAGED is used for products that can be used up and
* purchased multiple times (such as poker chips). It is up to the
* application to keep track of UNMANAGED products for the user.
*/
private enum Managed { MANAGED, UNMANAGED }
/**
* A {@link PurchaseObserver} is used to get callbacks when Android Market sends
* messages to this application so that we can update the UI.
*/
private class AppPurchaseObserver extends PurchaseObserver {
public AppPurchaseObserver(Handler handler) {
super(IrishSignAppActivity.this, handler);
}
public void onBillingSupported(boolean supported) {
if (Consts.DEBUG) {
Log.i(TAG, "supported: " + supported);
}
if (supported) {
restoreDatabase();
//ADD enabling methods or anything else you may need here...
}
//YOU can also put a dialog here
}
@Override
public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
int quantity, long purchaseTime, String developerPayload) {
if (Consts.DEBUG) {
Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " " + purchaseState);
}
if (purchaseState == PurchaseState.PURCHASED) {
ownedItems.add(itemId);
}
//YOU can also add other checks here
}
@Override
public void onRequestPurchaseResponse(RequestPurchase request,
ResponseCode responseCode) {
if (Consts.DEBUG) {
Log.d(TAG, request.mProductId + ": " + responseCode);
}
if (responseCode == ResponseCode.RESULT_OK) {
if (Consts.DEBUG) {
Log.i(TAG, "purchase was successfully sent to server");
}
//Do something here if you want
} else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
if (Consts.DEBUG) {
Log.i(TAG, "user canceled purchase");
}
} else {
if (Consts.DEBUG) {
Log.i(TAG, "purchase failed");
}
}
}
@Override
public void onRestoreTransactionsResponse(RestoreTransactions request,
ResponseCode responseCode) {
if (responseCode == ResponseCode.RESULT_OK) {
if (Consts.DEBUG) {
Log.d(TAG, "completed RestoreTransactions request");
}
// Update the shared preferences so that we don't perform
// a RestoreTransactions again.
SharedPreferences prefs = getSharedPreferences("idroprbilling",Context.MODE_PRIVATE);//NOTE: Change the idroprbilling to something else
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(DB_INITIALIZED, true);
edit.commit();
} else {
if (Consts.DEBUG) {
Log.d(TAG, "RestoreTransactions error: " + responseCode);
}
}
}
@Override
public void onBillingSupported(boolean supported, String type) {
// TODO Auto-generated method stub
}
}
//NOTE: This may not even apply to your app - copied here but may never be used if only purchasing an UPGRADE or PREMIUM service
private static class CatalogEntry {
public String sku;
public int nameId;
public Managed managed;
public CatalogEntry(String sku, int nameId, Managed managed) {
this.sku = sku;
this.nameId = nameId;
this.managed = managed;
}
}
//NOTE: Same as above - An array of product list entries for the products that can be purchased.
private static final CatalogEntry[] CATALOG = new CatalogEntry[] {
new CatalogEntry("Upgrade", R.string.upgrade, Managed.MANAGED),
new CatalogEntry("android.test.purchased", R.string.android_test_purchased, Managed.UNMANAGED),
new CatalogEntry("android.test.canceled", R.string.android_test_canceled, Managed.UNMANAGED),
new CatalogEntry("android.test.refunded", R.string.android_test_refunded, Managed.UNMANAGED),
new CatalogEntry("android.test.item_unavailable", R.string.android_test_item_unavailable, Managed.UNMANAGED),
};
/**
* If the database has not been initialized, we send a
* RESTORE_TRANSACTIONS request to Android Market to get the list of purchased items
* for this user. This happens if the application has just been installed
* or the user wiped data. We do not want to do this on every startup, rather, we want to do
* only when the database needs to be initialized.
*/
private void restoreDatabase() {
SharedPreferences prefs = getSharedPreferences("idroprbilling",MODE_PRIVATE);
boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
if (!initialized) {
billingService.restoreTransactions();
Toast.makeText(this, R.string.restoring_transactions, Toast.LENGTH_LONG).show();
}
}
/**
* Replaces the language and/or country of the device into the given string.
* The pattern "%lang%" will be replaced by the device's language code and
* the pattern "%region%" will be replaced with the device's country code.
*
* @param str the string to replace the language/country within
* @return a string containing the local language and region codes
*/
//NOTE: This method is here for convenience only
private String replaceLanguageAndRegion(String str) {
// Substitute language and or region if present in string
if (str.contains("%lang%") || str.contains("%region%")) {
Locale locale = Locale.getDefault();
str = str.replace("%lang%", locale.getLanguage().toLowerCase());
str = str.replace("%region%", locale.getCountry().toLowerCase());
}
return str;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
//Now setup the in-app billing</pre>
handler = new Handler();
appPurchaseObserver = new AppPurchaseObserver(handler);
billingService = new BillingService();
billingService.setContext(this);
purchaseDatabase = new PurchaseDatabase(this);
ResponseHandler.register(appPurchaseObserver);
/**
* Creates a background thread that reads the database and initializes the
* set of owned items.
*/
//Check if billing is supported. (Optional)
boolean check = billingService.checkBillingSupported();
setContentView(R.layout.main);
Button Language = (Button) findViewById(R.id.language);
Button A = (Button) findViewById(R.id.alphabet);
Button purchaseableItem = (Button) findViewById(R.id.topics);
Button purchaseableItem2 = (Button) findViewById(R.id.intro);
Button G = (Button) findViewById(R.id.about);
G.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
AlertDialog alertDialog = new AlertDialog.Builder(
IrishSignAppActivity.this).setCancelable(false)
.create(); // Reads Update
alertDialog.setTitle("Welcome ");
alertDialog.setMessage("this is my first dialog box :)");// Change tommorow//
alertDialog.setButton("Continue..",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int arg1) {
Intent our3intent = new Intent(
"com.IrishSign.app.MAIN"); // Change to
// revert
// back to
// menu
startActivity(our3intent);
}
});
alertDialog.show(); // <-- Shows dialog on screen.
}
});
purchaseableItem2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent our3intent = new Intent("com.IrishSign.app.Intro");
startActivity(our3intent);
}
});
purchaseableItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
//Now setup the in-app billing</pre>
boolean val = billingService.requestPurchase("com.irishsign.app.topicvideos", payloadContents, null);
}
});
A.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent ourintent = new Intent("com.IrishSign.app.alpha");
startActivity(ourintent);
}
});
Language.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Intent language = new Intent("com.IrishSign.app.Language");
startActivity(language);
}
});
}
}
The In-app billing sample implementation from Google is overly complex and uses unorthodox methods in a couple of areas. I can't explain why they did it this way but this is how I've worked around it.
First you can assume the service is 'started' and ready to be used when you initialised it in onCreate and gave it a context.
You load the Google Market purchase screen by using this line:
this.billingService.requestPurchase(<String ID of the in-app product>, null);
If the user purchases the item successfully, Market will broadcast a message to AppPurchaseObserver.onPurchaseStateChange() that you have set up to listen for them. In onPurchaseStateChange(), you'll want to do two things:
Store the fact that this user now owns this item in your own datastore on the app.
Update current activity to show new purchase (i.e. the dialog you are showing to the user that they don't own the item can now be closed.). Because AppPurchaseObserver is a nested class within your activity, onPurchaseStateChange() has access to the activity's view objects.
The thing to realise is that there is no way to query what items the user has purchased from Market in a fast, constant manner so Google actually requires that you store this info in your own data store when notified (as per 1. above). When you load up your own activities, you just query your own datastore of what the user purchased and load the right visual cues accordingly.