title: In-App-Billing - BillingHelper no fully instantiated
body: <p><strong>What I try to do</strong></p>
  
  <hr>
  
  <p>Hello Guys, I'm trying to create some In-App-Products for my App, its for donation use. Because im giving away free musik of my friend - he raps. Whatever, I created 5 In-App Products on my developer-account:</p>
  
  <ul>
  <li>donate_small</li>
  <li>donate_midsmall</li>
  <li>donate_medium</li>
  <li>donate_large</li>
  <li>donate_xlarge</li>
  </ul>
  
  <p>This are the reference key's I generated there. They are saved and published. Now if written a In-App-Service over this tutorial: <a href="http://www.anddev.org/advanced-tutorials-f21/simple-inapp-billing-payment-t52060.html" rel="noreferrer">In-App Easy Tutorial</a>. The Code seems to work perfect, there are no errors and when i compile the demo-code it works. But when I try it I get allways this error:</p>
  
  <pre><code>12-06 14:23:49.400: E/BillingService(4719): BillingHelper not fully instantiated
  </code></pre>
  
  <p><strong>Question</strong></p>
  
  <hr>
  
  <p>So what do I need to change the the user can choose the diffrent in-app-products? Do I need to declare them somewhere?? Also if you have a great tutorial for me, where this stuff is all well describen and fully working, please tell me.</p>
  
  <p>This are the Classes I use for my In-app-service:</p>
  
  <p><strong>de.stepforward</strong></p>
  
  <ul>
  <li>ChannelActivity</li>
  <li>SplashActivity</li>
  </ul>
  
  <p><strong>de.stepforward.billing</strong></p>
  
  <ul>
  <li>AppMainTest.class -> This is my Activity</li>
  <li>BillingHelper.class</li>
  <li>BillingReceiver.class</li>
  <li>BillingSecurity.class</li>
  <li>BillingService.class</li>
  <li>C.class</li>
  </ul>
  
  <p><strong>de.stepforward.billing.util</strong></p>
  
  <ul>
  <li>Base64.class</li>
  <li>Base64DecoderException.class</li>
  </ul>
  
  <p>For the moment I'll provide you the Code of my BillingHelper and Activity, if you need more Code just tell me.</p>
  
  <p><strong>Code</strong></p>
  
  <hr>
  
  <p>AppMainTest.class</p>
  
  <pre><code>package de.stepforward.billing;
  
  
  import de.stepforward.R;
  import de.stepforward.R.id;
  import de.stepforward.R.layout;
  import android.app.Activity;
  import android.content.Context;
  import android.content.Intent;
  import android.os.Bundle;
  import android.os.Handler;
  import android.util.Log;
  import android.view.View;
  import android.view.View.OnClickListener;
  import android.widget.Button;
  import android.widget.ImageView;
  
  public class AppMainTest extends Activity implements OnClickListener{
  
      private static final String TAG = "BillingService";
  
      private Context mContext;
      private ImageView purchaseableItem;
      private Button purchaseButton;
  
      /** Called when the activity is first created. */
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          Log.i("BillingService", "Starting");
          setContentView(R.layout.donate);
  
          mContext = this;
  
          purchaseButton = (Button) findViewById(R.id.main_purchase_yes);
          purchaseButton.setOnClickListener(this);
          purchaseableItem = (ImageView) findViewById(R.id.main_purchase_item);
  
          startService(new Intent(mContext, BillingService.class));
          BillingHelper.setCompletedHandler(mTransactionHandler);
      }
  
      public Handler mTransactionHandler = new Handler(){
              public void handleMessage(android.os.Message msg) {
                  Log.i(TAG, "Transaction complete");
                  Log.i(TAG, "Transaction status: "+BillingHelper.latestPurchase.purchaseState);
                  Log.i(TAG, "Item purchased is: "+BillingHelper.latestPurchase.productId);
  
                  if(BillingHelper.latestPurchase.isPurchased()){
                      showItem();
                  }
              };
  
      };
  
      public void onClick(View v) {
          switch (v.getId()) {
          case R.id.main_purchase_yes:
              if(BillingHelper.isBillingSupported()){
                  BillingHelper.requestPurchase(mContext, "android.test.purchased"); 
                  // android.test.purchased or android.test.canceled or android.test.refunded or com.blundell.item.passport
              } else {
                  Log.i(TAG,"Can't purchase on this device");
                  purchaseButton.setEnabled(false); // XXX press button before service started will disable when it shouldnt
              }
  
              break;
          default:
              // nada
              Log.i(TAG,"default. ID: "+v.getId());
              break;
          }
  
      }
  
      private void showItem() {
          purchaseableItem.setVisibility(View.VISIBLE);
      }
  
      @Override
      protected void onPause() {
          Log.i(TAG, "onPause())");
          super.onPause();
      }
  
      @Override
      protected void onDestroy() {
          BillingHelper.stopService();
          super.onDestroy();
      }
  }
  </code></pre>
  
  <p>BillingHelper.class</p>
  
  <pre><code>package de.stepforward.billing;
  
  import java.util.ArrayList;
  
  import android.app.PendingIntent;
  import android.app.PendingIntent.CanceledException;
  import android.content.Context;
  import android.content.Intent;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.RemoteException;
  import android.util.Log;
  
  import com.android.vending.billing.IMarketBillingService;
  
  import de.stepforward.billing.BillingSecurity.VerifiedPurchase;
  import de.stepforward.billing.C.ResponseCode;
  
  public class BillingHelper {
  
      private static final String TAG = "BillingService";
  
      private static IMarketBillingService mService;
      private static Context mContext;
      private static Handler mCompletedHandler;
  
      protected static VerifiedPurchase latestPurchase;
  
      protected static void instantiateHelper(Context context, IMarketBillingService service) {
          mService = service;
          mContext = context;
      }
  
      protected static void setCompletedHandler(Handler handler){
          mCompletedHandler = handler;
      }
  
      protected static boolean isBillingSupported() {
          if (amIDead()) {
              return false;
          }
          Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
          if (mService != null) {
              try {
                  Bundle response = mService.sendBillingRequest(request);
                  ResponseCode code = ResponseCode.valueOf((Integer) response.get("RESPONSE_CODE"));
                  Log.i(TAG, "isBillingSupported response was: " + code.toString());
                  if (ResponseCode.RESULT_OK.equals(code)) {
                      return true;
                  } else {
                      return false;
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "isBillingSupported response was: RemoteException", e);
                  return false;
              }
          } else {
              Log.i(TAG, "isBillingSupported response was: BillingService.mService = null");
              return false;
          }
      }
  
      /**
       * A REQUEST_PURCHASE request also triggers two asynchronous responses (broadcast intents). 
       * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides error information about the request. (which I ignore)
       * Next, if the request was successful, the Android Market application sends an IN_APP_NOTIFY broadcast intent. 
       * This message contains a notification ID, which you can use to retrieve the transaction details for the REQUEST_PURCHASE
       * @param activityContext
       * @param itemId
       */
      protected static void requestPurchase(Context activityContext, String itemId){
          if (amIDead()) {
              return;
          }
          Log.i(TAG, "requestPurchase()");
          Bundle request = makeRequestBundle("REQUEST_PURCHASE");
          request.putString("ITEM_ID", itemId);
          try {
              Bundle response = mService.sendBillingRequest(request);
  
              //The RESPONSE_CODE key provides you with the status of the request
              Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");
              //The PURCHASE_INTENT key provides you with a PendingIntent, which you can use to launch the checkout UI
              PendingIntent pendingIntent = (PendingIntent) response.get("PURCHASE_INTENT");
              //The REQUEST_ID key provides you with a unique request identifier for the request
              Long requestIndentifier     = (Long) response.get("REQUEST_ID");
              Log.i(TAG, "current request is:" + requestIndentifier);
              C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
              Log.i(TAG, "REQUEST_PURCHASE Sync Response code: "+responseCode.toString());
  
              startBuyPageActivity(pendingIntent, new Intent(), activityContext);
          } catch (RemoteException e) {
              Log.e(TAG, "Failed, internet error maybe", e);
              Log.e(TAG, "Billing supported: "+isBillingSupported());
          }
      }
  
      /**
       * A GET_PURCHASE_INFORMATION request also triggers two asynchronous responses (broadcast intents). 
       * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides status and error information about the request.  (which I ignore)
       * Next, if the request was successful, the Android Market application sends a PURCHASE_STATE_CHANGED broadcast intent. 
       * This message contains detailed transaction information. 
       * The transaction information is contained in a signed JSON string (unencrypted). 
       * The message includes the signature so you can verify the integrity of the signed string
       * @param notifyIds
       */
      protected static void getPurchaseInformation(String[] notifyIds){
          if (amIDead()) {
              return;
          }
          Log.i(TAG, "getPurchaseInformation()");
          Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
          // The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate.
          // The Android Market application returns this nonce with the PURCHASE_STATE_CHANGED broadcast intent so you can verify the integrity of the transaction information.
          request.putLong("NONCE", BillingSecurity.generateNonce());
          // The NOTIFY_IDS key contains an array of notification IDs, which you received in the IN_APP_NOTIFY broadcast intent.
          request.putStringArray("NOTIFY_IDS", notifyIds);
          try {
              Bundle response = mService.sendBillingRequest(request);
  
              //The REQUEST_ID key provides you with a unique request identifier for the request
              Long requestIndentifier     = (Long) response.get("REQUEST_ID");
              Log.i(TAG, "current request is:" + requestIndentifier);
              //The RESPONSE_CODE key provides you with the status of the request
              Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");
              C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
              Log.i(TAG, "GET_PURCHASE_INFORMATION Sync Response code: "+responseCode.toString());
  
          } catch (RemoteException e) {
              Log.e(TAG, "Failed, internet error maybe", e);
              Log.e(TAG, "Billing supported: "+isBillingSupported());
          }
      }
  
      /**
       * To acknowledge that you received transaction information you send a
       * CONFIRM_NOTIFICATIONS request.
       * 
       * A CONFIRM_NOTIFICATIONS request triggers a single asynchronous response—a RESPONSE_CODE broadcast intent. 
       * This broadcast intent provides status and error information about the request.
       * 
       * Note: As a best practice, you should not send a CONFIRM_NOTIFICATIONS request for a purchased item until you have delivered the item to the user. 
       * This way, if your application crashes or something else prevents your application from delivering the product,
       * your application will still receive an IN_APP_NOTIFY broadcast intent from Android Market indicating that you need to deliver the product
       * @param notifyIds
       */
      protected static void confirmTransaction(String[] notifyIds) {
          if (amIDead()) {
              return;
          }
          Log.i(TAG, "confirmTransaction()");
          Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
          request.putStringArray("NOTIFY_IDS", notifyIds);
          try {
              Bundle response = mService.sendBillingRequest(request);
  
              //The REQUEST_ID key provides you with a unique request identifier for the request
              Long requestIndentifier     = (Long) response.get("REQUEST_ID");
              Log.i(TAG, "current request is:" + requestIndentifier);
  
              //The RESPONSE_CODE key provides you with the status of the request
              Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");
              C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
  
              Log.i(TAG, "CONFIRM_NOTIFICATIONS Sync Response code: "+responseCode.toString());
          } catch (RemoteException e) {
              Log.e(TAG, "Failed, internet error maybe", e);
              Log.e(TAG, "Billing supported: " + isBillingSupported());
          }
      }
  
      /**
       * 
       * Can be used for when a user has reinstalled the app to give back prior purchases. 
       * if an item for sale's purchase type is "managed per user account" this means google will have a record ofthis transaction
       * 
       * A RESTORE_TRANSACTIONS request also triggers two asynchronous responses (broadcast intents). 
       * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides status and error information about the request. 
       * Next, if the request was successful, the Android Market application sends a PURCHASE_STATE_CHANGED broadcast intent. 
       * This message contains the detailed transaction information. The transaction information is contained in a signed JSON string (unencrypted).
       * The message includes the signature so you can verify the integrity of the signed string
       * @param nonce
       */
      protected static void restoreTransactionInformation(Long nonce) {
          if (amIDead()) {
              return;
          }
          Log.i(TAG, "confirmTransaction()");
          Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
          // The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate
          request.putLong("NONCE", nonce);
          try {
              Bundle response = mService.sendBillingRequest(request);
  
              //The REQUEST_ID key provides you with a unique request identifier for the request
              Long requestIndentifier     = (Long) response.get("REQUEST_ID");
              Log.i(TAG, "current request is:" + requestIndentifier);
  
              //The RESPONSE_CODE key provides you with the status of the request
              Integer responseCodeIndex   = (Integer) response.get("RESPONSE_CODE");
              C.ResponseCode responseCode = C.ResponseCode.valueOf(responseCodeIndex);
              Log.i(TAG, "RESTORE_TRANSACTIONS Sync Response code: "+responseCode.toString());
          } catch (RemoteException e) {
              Log.e(TAG, "Failed, internet error maybe", e);
              Log.e(TAG, "Billing supported: " + isBillingSupported());
          }
      }
  
      private static boolean amIDead() {
          if (mService == null || mContext == null) {
              Log.e(TAG, "BillingHelper not fully instantiated");
              return true;
          } else {
              return false;
          }
      }
  
      private static Bundle makeRequestBundle(String method) {
          Bundle request = new Bundle();
          request.putString("BILLING_REQUEST", method);
          request.putInt("API_VERSION", 1);
          request.putString("PACKAGE_NAME", mContext.getPackageName());
          return request;
      }
  
      /**
       * 
       * 
       * You must launch the pending intent from an activity context and not an application context
       * You cannot use the singleTop launch mode to launch the pending intent
       * @param pendingIntent
       * @param intent
       * @param context
       */
      private static void startBuyPageActivity(PendingIntent pendingIntent, Intent intent, Context context){
          //TODO add above 2.0 implementation with reflection, for now just using 1.6 implem
  
          // This is on Android 1.6. The in-app checkout page activity will be on its
          // own separate activity stack instead of on the activity stack of
          // the application.
          try {
              pendingIntent.send(context, 0, intent);         
          } catch (CanceledException e){
              Log.e(TAG, "startBuyPageActivity CanceledException");
          }
      }
  
      protected static void verifyPurchase(String signedData, String signature) {
          ArrayList&lt;VerifiedPurchase&gt; purchases = BillingSecurity.verifyPurchase(signedData, signature);
          latestPurchase = purchases.get(0);
  
          confirmTransaction(new String[]{latestPurchase.notificationId});
  
          if(mCompletedHandler != null){
              mCompletedHandler.sendEmptyMessage(0);
          } else {
              Log.e(TAG, "verifyPurchase error. Handler not instantiated. Have you called setCompletedHandler()?");
          }
      }
  
      public static void stopService(){
          mContext.stopService(new Intent(mContext, BillingService.class));
          mService = null;
          mContext = null;
          mCompletedHandler = null;
          Log.i(TAG, "Stopping Service");
      }
  }
  </code></pre>
  
  <hr>
  
  <p>Thx for your Help in Advance</p>
  
  <p>Best Regards</p>
  
  <p>safari</p>
  
  <p><strong>Appendix</strong></p>
  
  <hr>
  
  <p>Manifest</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="de.stepforward"
      android:versionCode="6"
      android:versionName="1.3.2" &gt;
  
      &lt;uses-sdk android:minSdkVersion="10" /&gt;
  
      &lt;application
          android:icon="@drawable/ic_launcher"
          android:label="@string/app_name"
          android:theme="@style/StepForward.Theme"
          android:debuggable="false"
           &gt;
          &lt;activity
              android:label="@string/app_name"
              android:name=".SplashActivity" &gt;
              &lt;intent-filter &gt;
                  &lt;action android:name="android.intent.action.MAIN" /&gt;
  
                  &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
              &lt;/intent-filter&gt;
          &lt;/activity&gt;
              &lt;activity android:name=".ChannelActivity" &gt;&lt;/activity&gt;
              &lt;activity android:name=".billing.AppMainTest"&gt;&lt;/activity&gt;
      &lt;/application&gt;
              &lt;uses-permission android:name="android.permission.INTERNET" /&gt;
              &lt;uses-permission android:name="com.android.vending.BILLING" /&gt;
  
  
   &lt;!--  In-App-Einkäufe für Donate --&gt;   
      &lt;service android:name=".BillingService" /&gt;
  
      &lt;receiver android:name=".BillingReceiver"&gt;
        &lt;intent-filter&gt;
          &lt;action android:name="com.android.vending.billing.IN_APP_NOTIFY" /&gt;
          &lt;action android:name="com.android.vending.billing.RESPONSE_CODE" /&gt;
          &lt;action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" /&gt;
        &lt;/intent-filter&gt;
      &lt;/receiver&gt;
  
  &lt;/manifest&gt;
  </code></pre>
  
  <p><strong>LogCat (some more information)</strong></p>
  
  <pre><code>12-07 13:58:14.334: I/BillingService(20997): Starting
  12-07 13:58:14.364: D/dalvikvm(20997): GC_EXTERNAL_ALLOC freed 15K, 58% free 2919K/6791K, external 414K/926K, paused 22ms
  12-07 13:58:23.864: E/BillingService(20997): BillingHelper not fully instantiated
  12-07 13:58:23.864: I/BillingService(20997): Can't purchase on this device
  </code></pre>