Android in-app billing - exception when pressing buy, and then pressing back from the purchase screen

StackOverflow https://stackoverflow.com/questions/11456246

  •  20-06-2021
  •  | 
  •  

Question

When the user presses the buy button, they get taken to the android play screen to buy the in-app item. But when they press back, they get this exception about not being able to start the billing service:

java.lang.RuntimeException: Unable to start service com.problemio.BillingService@405704f0 with Intent { act=com.android.vending.billing.RESPONSE_CODE cmp=com.problemio/.BillingService (has extras) }: java.lang.NullPointerException
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2387)
at android.app.ActivityThread.access$2800(ActivityThread.java:132)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1111)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:4293)
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.NullPointerException
at com.problemio.ExtraHelpActivity.prependLogEntry(ExtraHelpActivity.java:561)
at com.problemio.ExtraHelpActivity.logProductActivity(ExtraHelpActivity.java:569)
at com.problemio.ExtraHelpActivity.access$4(ExtraHelpActivity.java:565)
at com.problemio.ExtraHelpActivity$ExtraHelpPurchaseObserver.onRequestPurchaseResponse(ExtraHelpActivity.java:187)
at com.problemio.ResponseHandler.responseCodeReceived(ResponseHandler.java:137)
at com.problemio.BillingService$RequestPurchase.responseCodeReceived(BillingService.java:285)
at com.problemio.BillingService.checkResponseCode(BillingService.java:582)
at com.problemio.BillingService.handleCommand(BillingService.java:427)
at com.problemio.BillingService.onStart(BillingService.java:398)
at android.app.Service.onStartCommand(Service.java:428)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2370)
... 10 more
java.lang.NullPointerException
at com.problemio.ExtraHelpActivity.prependLogEntry(ExtraHelpActivity.java:561)
at com.problemio.ExtraHelpActivity.logProductActivity(ExtraHelpActivity.java:569)
at com.problemio.ExtraHelpActivity.access$4(ExtraHelpActivity.java:565)
at com.problemio.ExtraHelpActivity$ExtraHelpPurchaseObserver.onRequestPurchaseResponse(ExtraHelpActivity.java:187)
at com.problemio.ResponseHandler.responseCodeReceived(ResponseHandler.java:137)
at com.problemio.BillingService$RequestPurchase.responseCodeReceived(BillingService.java:285)
at com.problemio.BillingService.checkResponseCode(BillingService.java:582)
at com.problemio.BillingService.handleCommand(BillingService.java:427)
at com.problemio.BillingService.onStart(BillingService.java:398)
at android.app.Service.onStartCommand(Service.java:428)
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2370)
at android.app.ActivityThread.access$2800(ActivityThread.java:132)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1111)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:4293)
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)

It specifically mentions these lines in my class:

Caused by:

java.lang.NullPointerException
at com.problemio.ExtraHelpActivity.prependLogEntry(ExtraHelpActivity.java:561)
at com.problemio.ExtraHelpActivity.logProductActivity(ExtraHelpActivity.java:569)
at com.problemio.ExtraHelpActivity.access$4(ExtraHelpActivity.java:565)
at com.problemio.ExtraHelpActivity$ExtraHelpPurchaseObserver.onRequestPurchaseResponse(ExtraHelpActivity.java:187)

on line 561 I have this code:

private void prependLogEntry(CharSequence cs) {
    SpannableStringBuilder contents = new SpannableStringBuilder(cs);
    contents.append('\n');
    contents.append(mLogTextView.getText());   // Line 561
    mLogTextView.setText(contents);
}

on line 569 I have this code:

private void logProductActivity(String product, String activity) {
    SpannableStringBuilder contents = new SpannableStringBuilder();
    contents.append(Html.fromHtml("<b>" + product + "</b>: "));
    contents.append(activity);
    prependLogEntry(contents); // Line 569
}

By the way, which log is this? Is it really needed? Maybe its worthwhile to comment out these methods?

In any case, the other line the error points to is 187 and here is the code for that:

@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");
        }
        logProductActivity(request.mProductId, "sending purchase request");
    } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
        if (Consts.DEBUG) {
            Log.i(TAG, "user canceled purchase");
        }
        logProductActivity(request.mProductId, "dismissed purchase dialog");  
        // This is line 187 right above this comment.
    } else {
        if (Consts.DEBUG) {
            Log.i(TAG, "purchase failed");
        }
        logProductActivity(request.mProductId, "request purchase returned " + responseCode);
    }
}

So you see, it keeps having a problem on this logging thing. What is this log? Where can I see it? How can I fix it so it doesn't make errors?

I am not certain, but maybe the bigger issue is that request is null. Why would the request possibly be null here?

Here is the entire ExtraHelpActivity class:

public class ExtraHelpActivity extends BaseActivity implements ServiceConnection 
{
    // , OnClickListener 
    private static final String TAG = "Pay";

    String issueProductIdWebMarketing = "1";    
    String issueProductIdDonate = "2";  
    String issueProductIdPsych = "3";   

    /**
    * The developer payload that is sent with subsequent
    * purchase requests.
    */
    private String payloadContents = null;

    /**
     * Used for storing the log text.
     */
    private static final String LOG_TEXT_KEY = "DUNGEONS_LOG_TEXT";

    /**
     * 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 ExtraHelpPurchaseObserver mExtraHelpPurchaseObserver;
    private Handler mHandler;
    private Handler handler;


    private BillingService mBillingService;
    private Button mBuyButton;
    private Button mEditPayloadButton;
    private Button mEditSubscriptionsButton;
    private TextView mLogTextView;
    private Spinner mSelectItemSpinner;
    private ListView mOwnedItemsTable;
    private SimpleCursorAdapter mOwnedItemsAdapter;
    private PurchaseDatabase mPurchaseDatabase;
    private Cursor mOwnedItemsCursor;
    private Set<String> mOwnedItems = new HashSet<String>();

    /**
     * The developer payload that is sent with subsequent
     * purchase requests.
     */
    private String mPayloadContents = null;

    private static final int DIALOG_CANNOT_CONNECT_ID = 1;
    private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2;
    private static final int DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID = 3;

    /**
     * Each product in the catalog can be MANAGED, UNMANAGED, or SUBSCRIPTION.  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.
     * SUBSCRIPTION is just like MANAGED except that the user gets charged monthly
     * or yearly.
     */
    private enum Managed { MANAGED, UNMANAGED, SUBSCRIPTION }

    /**
     * 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 ExtraHelpPurchaseObserver extends PurchaseObserver {
        public ExtraHelpPurchaseObserver(Handler handler) {
            super(ExtraHelpActivity.this, handler);
        }

        @Override
        public void onBillingSupported(boolean supported, String type) {
            if (Consts.DEBUG) {
                Log.i(TAG, "supported: " + supported);
            }
            if (type == null || type.equals(Consts.ITEM_TYPE_INAPP)) {
                if (supported) {
                    restoreDatabase();
                    mBuyButton.setEnabled(true);
                    mEditPayloadButton.setEnabled(true);
                } else {
                    showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
                }
            } else if (type.equals(Consts.ITEM_TYPE_SUBSCRIPTION)) {
                mCatalogAdapter.setSubscriptionsSupported(supported);
            } else {
                showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
            }
        }

        @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 (developerPayload == null) {
                logProductActivity(itemId, purchaseState.toString());
            } else {
                logProductActivity(itemId, purchaseState + "\n\t" + developerPayload);
            }

            if (purchaseState == PurchaseState.PURCHASED) {
                mOwnedItems.add(itemId);

                // If this is a subscription, then enable the "Edit
                // Subscriptions" button.
                for (CatalogEntry e : CATALOG) {
                    if (e.sku.equals(itemId) &&
                            e.managed.equals(Managed.SUBSCRIPTION)) {
                        mEditSubscriptionsButton.setVisibility(View.VISIBLE);
                    }
                }
            }
            mCatalogAdapter.setOwnedItems(mOwnedItems);
            mOwnedItemsCursor.requery();
        }

        @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");
                }
                logProductActivity(request.mProductId, "sending purchase request");
            } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
                if (Consts.DEBUG) {
                    Log.i(TAG, "user canceled purchase");
                }
                logProductActivity(request.mProductId, "dismissed purchase dialog");
            } else {
                if (Consts.DEBUG) {
                    Log.i(TAG, "purchase failed");
                }
                logProductActivity(request.mProductId, "request purchase returned " + responseCode);
            }
        }

        @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 = getPreferences(Context.MODE_PRIVATE);
                SharedPreferences.Editor edit = prefs.edit();
                edit.putBoolean(DB_INITIALIZED, true);
                edit.commit();
            } else {
                if (Consts.DEBUG) {
                    Log.d(TAG, "RestoreTransactions error: " + responseCode);
                }
            }
        }
    }

    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;
        }
    }

    /** An array of product list entries for the products that can be purchased. */
    private static final CatalogEntry[] CATALOG = new CatalogEntry[] {
        new CatalogEntry("marketing_001", 1 , Managed.MANAGED),
        new CatalogEntry("potion_001", 2 , Managed.UNMANAGED),
        new CatalogEntry("subscription_monthly", 3,
                Managed.SUBSCRIPTION),
        new CatalogEntry("subscription_yearly", 4 ,
                Managed.SUBSCRIPTION)
    };  


    private String mItemName;
    private String mSku;
    private Managed mManagedType;
    private CatalogAdapter mCatalogAdapter; 


  //outside onCreate() Within class
//    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()){
//               //code here which is to be performed after successful purchase
//           }
//       };
//
//    };    


    // TODO: 
    // TODO: 
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
            super.onCreate(savedInstanceState);

            setContentView(R.layout.extra_help);

            //Now setup the in-app billing</pre>
            handler = new Handler();            

            mExtraHelpPurchaseObserver = new ExtraHelpPurchaseObserver(handler); 

            mBillingService = new BillingService();

            mBillingService.setContext(this);
            mPurchaseDatabase = new PurchaseDatabase(this);

            ResponseHandler.register(mExtraHelpPurchaseObserver);

            //Check if billing is supported. (Optional)
            //boolean check = mBillingService.checkBillingSupported();




//              startService(new Intent(mContext, BillingService.class));
//              BillingHelper.setCompletedHandler(mTransactionHandler);             

            // Make sure the user is logged in
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( ExtraHelpActivity.this);
            final String user_id = prefs.getString( "user_id" , null );        

            Button donate = (Button)findViewById(R.id.donate); 
            donate.setOnClickListener(new Button.OnClickListener() 
            {  
               public void onClick(View v) 
               {

                   // Send me an email that a comment was submitted on a question. 

//                    boolean val = mBillingService.requestPurchase(
//                            "android.test.purchased", payloadContents);
                  //Replace "android.test.purchased" with your product ID that you added to Google Play or make it a variable that is populated from a list.
                  //Place this code in a button event or where ever it fits in your process flow.

                  if (mBillingService.requestPurchase(issueProductIdDonate, Consts.ITEM_TYPE_INAPP ,  null)) 
                  {

                  } 
                  else 
                  {

                        Log.i("tag", "Can't purchase on this device");

                  }
               }
            });                 


            try 
            {
                  boolean bindResult = bindService(
                    new Intent("com.android.vending.billing.MarketBillingService.BIND"), 
                    this,
                    Context.BIND_AUTO_CREATE);
                  if (bindResult) 
                  {
                    Log.i( "Err" , "Service bind successful.");
                  } 
                  else 
                  {
                    Log.e( "Err", "Could not bind to the MarketBillingService.");
                  }
            } 
            catch (SecurityException e) 
            {
                  Log.e( "Err" , "Security exception: " + e);
            }           

    }




    /**
     * Save the context of the log so simple things like rotation will not
     * result in the log being cleared.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) 
    {
        super.onSaveInstanceState(outState);
        //outState.putString(LOG_TEXT_KEY, Html.toHtml((Spanned) mLogTextView.getText()));
    }

    /**
     * Restore the contents of the log if it has previously been saved.
     */
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) 
    {
        super.onRestoreInstanceState(savedInstanceState);
        if (savedInstanceState != null) 
        {
            //mLogTextView.setText(Html.fromHtml(savedInstanceState.getString(LOG_TEXT_KEY)));
        }
    }

    @Override
    protected Dialog onCreateDialog(int id) 
    {
        switch (id) 
        {
            case DIALOG_CANNOT_CONNECT_ID:
            return createDialog(1,1);
        case DIALOG_BILLING_NOT_SUPPORTED_ID:
            return createDialog(2,2);
            case DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID:
                return createDialog(3,3);

        //          case DIALOG_CANNOT_CONNECT_ID:
//              return createDialog(R.string.cannot_connect_title,
//                      R.string.cannot_connect_message);
//          case DIALOG_BILLING_NOT_SUPPORTED_ID:
//              return createDialog(R.string.billing_not_supported_title,
//                      R.string.billing_not_supported_message);
//              case DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID:
//                  return createDialog(R.string.subscriptions_not_supported_title,
//                          R.string.subscriptions_not_supported_message);
            default:
                return null;
        }
    }

    private Dialog createDialog(int titleId, int messageId) {
        String helpUrl = replaceLanguageAndRegion("help_url");
        if (Consts.DEBUG) {
            Log.i(TAG, helpUrl);
        }
        final Uri helpUri = Uri.parse(helpUrl);

        // TODO: replace 1 with the thing its supposed to be - I think learn more url :)
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(titleId)
            .setIcon(android.R.drawable.stat_sys_warning)
            .setMessage(messageId)
            .setCancelable(false)
            .setPositiveButton(android.R.string.ok, null)
            .setNegativeButton(1, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Intent intent = new Intent(Intent.ACTION_VIEW, helpUri);
                    startActivity(intent);
                }
            });
        return builder.create();
    }

    /**
     * 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
     */
    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;
    }

    /**
     * Sets up the UI.
     */
    private void setupWidgets() 
    {
        mOwnedItemsCursor = mPurchaseDatabase.queryAllPurchasedItems();
        startManagingCursor(mOwnedItemsCursor);
        String[] from = new String[] { PurchaseDatabase.PURCHASED_PRODUCT_ID_COL,
                PurchaseDatabase.PURCHASED_QUANTITY_COL
        };
//        int[] to = new int[] { R.id.item_name, R.id.item_quantity };
//        mOwnedItemsAdapter = new SimpleCursorAdapter(this, R.layout.item_row,
//                mOwnedItemsCursor, from, to);
//        mOwnedItemsTable = (ListView) findViewById(R.id.owned_items);
//        mOwnedItemsTable.setAdapter(mOwnedItemsAdapter);
    }

    private void prependLogEntry(CharSequence cs) {
        SpannableStringBuilder contents = new SpannableStringBuilder(cs);
        contents.append('\n');
        contents.append(mLogTextView.getText());
        mLogTextView.setText(contents);
    }

    private void logProductActivity(String product, String activity) {
        SpannableStringBuilder contents = new SpannableStringBuilder();
        contents.append(Html.fromHtml("<b>" + product + "</b>: "));
        contents.append(activity);
        prependLogEntry(contents);
    }

    /**
     * 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 = getPreferences(MODE_PRIVATE);
        boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
        if (!initialized) {
            mBillingService.restoreTransactions();
            Toast.makeText(this, 3, Toast.LENGTH_LONG).show(); 
            // Used to be R.string.restoring_transactions instead of 3
        }
    }

    /**
     * Creates a background thread that reads the database and initializes the
     * set of owned items.
     */
    private void initializeOwnedItems() {
        new Thread(new Runnable() {
            public void run() {
                doInitializeOwnedItems();
            }
        }).start();
    }

    /**
     * Reads the set of purchased items from the database in a background thread
     * and then adds those items to the set of owned items in the main UI
     * thread.
     */
    private void doInitializeOwnedItems() {
        Cursor cursor = mPurchaseDatabase.queryAllPurchasedItems();
        if (cursor == null) {
            return;
        }

        final Set<String> ownedItems = new HashSet<String>();
        try {
            int productIdCol = cursor.getColumnIndexOrThrow(
                    PurchaseDatabase.PURCHASED_PRODUCT_ID_COL);
            while (cursor.moveToNext()) {
                String productId = cursor.getString(productIdCol);
                ownedItems.add(productId);
            }
        } finally {
            cursor.close();
        }

        // We will add the set of owned items in a new Runnable that runs on
        // the UI thread so that we don't need to synchronize access to
        // mOwnedItems.
        mHandler.post(new Runnable() {
            public void run() {
                mOwnedItems.addAll(ownedItems);
                mCatalogAdapter.setOwnedItems(mOwnedItems);
            }
        });
    }

    /**
     * Called when a button is pressed.
     */
    public void onClick(View v) {
        if (v == mBuyButton) {
            if (Consts.DEBUG) {
                Log.d(TAG, "buying: " + mItemName + " sku: " + mSku);
            }

            if (mManagedType != Managed.SUBSCRIPTION &&
                    !mBillingService.requestPurchase(mSku, Consts.ITEM_TYPE_INAPP, mPayloadContents)) {
                showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
            } else if (!mBillingService.requestPurchase(mSku, Consts.ITEM_TYPE_SUBSCRIPTION, mPayloadContents)) {
                // Note: mManagedType == Managed.SUBSCRIPTION
                showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
            }
        } else if (v == mEditPayloadButton) {
            showPayloadEditDialog();
        } else if (v == mEditSubscriptionsButton) {
            editSubscriptions();
        }
    }

    /** List subscriptions for this package in Google Play
     *
     * This allows users to unsubscribe from this apps subscriptions.
     *
     * Subscriptions are listed on the Google Play app detail page, so this
     * should only be called if subscriptions are known to be present.
     */
    private void editSubscriptions() {
        // Get current package name
        String packageName = getPackageName();
        // Open app detail in Google Play
        Intent i = new Intent(Intent.ACTION_VIEW,
                             Uri.parse("market://details?id=" + packageName));
        startActivity(i);
    }

    /**
     * Displays the dialog used to edit the payload dialog.
     */
    private void showPayloadEditDialog() 
    {
        AlertDialog.Builder dialog = new AlertDialog.Builder(this);
        final View view = View.inflate(this, R.layout.edit_payload, null);
        final TextView payloadText = (TextView) view.findViewById(R.id.payload_text);
        if (mPayloadContents != null) {
            payloadText.setText(mPayloadContents);
        }

        dialog.setView(view);
        dialog.setPositiveButton(
                R.string.edit_payload_accept,
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        mPayloadContents = payloadText.getText().toString();
                    }
                });
        dialog.setNegativeButton(
                R.string.edit_payload_clear,
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        if (dialog != null) {
                            mPayloadContents = null;
                            dialog.cancel();
                        }
                    }
                });
        dialog.setOnCancelListener(new DialogInterface.OnCancelListener(
Was it helpful?

Solution

oops.

mLogTextView is never initialised.

add mLogTextView = findViewById(R.id.blaaa);

I am sure you already know that

please tell me if i am wrong so that I delete this answer (:

OTHER TIPS

Are you give permission in menifest file?

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

I am adding an answer because I reached the character limit in my original question since my class is so long.

This is the extra_help.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >        

<include android:id="@+id/header"
         layout="@layout/header"
         android:layout_height="wrap_content"
         android:layout_width="fill_parent"/>

<ScrollView
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" 
      android:padding="5px">    

<LinearLayout 

        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:padding="5px"
        >  

<TextView
    android:id="@+id/heading_1"    
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textColor="@color/light_best_blue"
    android:text="THE APP IS FREE TO HELP AS MANY PEOPLE AS POSSIBLE, PLEASE GIVE BACK"
    />

    <Button
    android:id="@+id/donate"
    android:layout_marginTop ="10dp"        
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Donate $1.99 Since the App is Free"
    /> 

</LinearLayout>

</ScrollView>

</LinearLayout>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top