Android In App Billing API : Monetize your app using Play billing library (Buy & Subscribe)

Android In App Billing API : Monetize your app using Play billing library (Buy & Subscribe)

 

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

Android In App Billing API provides a effortless and simple interface for sending In-app Billing requests, receiving response and managing In-app Billing transactions using Google Play. The step by step example android app will cover the basics of how to make calls from your app to Google Play Android In App Billing API Buy Subscribe Monetize app Play billing library.

 

Android has released In-App Billing API for developers to integrate and monetize their mobile apps.  Users able to

  1. Purchase Digital Products.
  2. Paid subscription.
  3. App upgrades/downgrades.
  4. Use promotion codes.
  5. Game Points.
  6. Game levels.
  7. News Paper subscriptions.
  8. Buy photos.
  9. Extend premium services.
  10. and many more interesting things .

As a developer you should have Google Play Console account and a Google Wallet merchant account in order to activate in app purchases in your app.

 

Product types in in-app billing API

 

  • Managed Products

Managed products can be anything that is purchased once and used life long. for example Digital products, photos, coins for the game  and etc..

  • Subscriptions

Subscriptions let you sell contents, services, digital products and etc for a recurring period of time. Users have to buy/renew every time when the recurring period finished.  For example you are selling subscription for your mobile news paper app for an extended period of time such as for one month, six month or one year. Google Play handles all checkout details so your app never has to directly process any financial transactions.


 

You should upload billing enabled APK

You Must upload an APK (with billing enabled) to playstore to create in-app products.

Your app doesn’t have any in-app products yet.
To add in-app products, you need to add the BILLING permission to your APK.
Android In App Billing API Buy Subscribe Monetize app Play billing library
To add in-app products, you need to add the BILLING permission to your APK.

 

 

Create An Android App

 

Create android app in Android studio and include the following billing library dependency into the build.gradle file

compile 'com.android.billingclient:billing:1.0'

And don’t forget to press “sync” button.

 

What i am going to do is,  In the main Activity there will be a button. if i click on that, it will show a list of In-App Products which we will create in the google play console.

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

Before you go through the code, i will describe you about one of the most important class which is BillingManager.java

It contains the most of the method which we needed for the business logic.

package com.appsgit.inappexample.billing;

import android.app.Activity;

import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.PurchaseHistoryResponseListener;
import com.android.billingclient.api.PurchasesUpdatedListener;
import android.util.Log;

import com.android.billingclient.api.BillingClient.BillingResponse;
import com.android.billingclient.api.BillingClient.SkuType;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.appsgit.inappexample.MainActivity;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;


public class BillingManager implements PurchasesUpdatedListener {
    private static final String TAG = BillingManager.class.getSimpleName();

    private final BillingClient mBillingClient;
    private final Activity mActivity;

    // Defining SKU constants from Google Play Developer Console
    private static final HashMap<String, List<String>> SKUS;
    static
    {
        SKUS = new HashMap<>();
        SKUS.put(SkuType.INAPP, Arrays.asList("theme1", "theme2"));
        SKUS.put(SkuType.SUBS, Arrays.asList("subscription_monthly", "subscription_yearly"));
    }

    public BillingManager(Activity activity) {
        mActivity = activity;
        mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
        startServiceConnectionIfNeeded(null);
    }

    @Override
    public void onPurchasesUpdated(int responseCode, List<Purchase> purchases) {
        Log.i(TAG, "onPurchasesUpdated() response: " + responseCode);

        if (responseCode == BillingResponse.OK) {
            ((MainActivity)mActivity).updateUi(purchases);
            consumePurchases(purchases);
        }

    }

    /*
    * By default you cannot buy the same product again and again...after you purchased the item you should cosume it.
    * For testing purpose i am going to consume all the product each and everytim i buy...
     */
    public void consumePurchases(List<Purchase> purchases) {
        try {
            if (purchases != null && purchases.size() > 0) {
                for (final Purchase purchase : purchases) {
                    if (purchase.getPurchaseToken() != null && !purchase.getPurchaseToken().isEmpty()) {
                        mBillingClient.consumeAsync(purchase.getPurchaseToken(), new ConsumeResponseListener() {
                            @Override
                            public void onConsumeResponse(int responseCode, String purchaseToken) {
                                Log.d(TAG, "onConsumeResponse: purchased SKU  " + purchase.getSku());
                                Log.d(TAG, "onConsumeResponse: purchased getPurchaseToken  " + purchase.getPurchaseToken());
                            }
                        });
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void startServiceConnectionIfNeeded(final Runnable executeOnSuccess) {
        if (mBillingClient.isReady()) {
            if (executeOnSuccess != null) {
                executeOnSuccess.run();
            }
        } else {
            mBillingClient.startConnection(new BillingClientStateListener() {
                @Override
                public void onBillingSetupFinished(@BillingResponse int billingResponse) {
                    if (billingResponse == BillingResponse.OK) {
                        Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
                        if (executeOnSuccess != null) {
                            executeOnSuccess.run();
                        }
                    } else {
                        Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
                    }
                }

                @Override
                public void onBillingServiceDisconnected() {
                    Log.w(TAG, "onBillingServiceDisconnected()");
                }
            });
        }
    }
    
    /to get the list of in-app products which we created in Google play console.
    public void querySkuDetailsAsync(@BillingClient.SkuType final String itemType,
            final List<String> skuList, final SkuDetailsResponseListener listener) {
        // Specify a runnable to start when connection to Billing client is established
        Runnable executeOnConnectedService = new Runnable() {
            @Override
            public void run() {
                SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
                        .setSkusList(skuList).setType(itemType).build();
                mBillingClient.querySkuDetailsAsync(skuDetailsParams,
                        new SkuDetailsResponseListener() {
                            @Override
                            public void onSkuDetailsResponse(int responseCode,
                                    List<SkuDetails> skuDetailsList) {
                                listener.onSkuDetailsResponse(responseCode, skuDetailsList);
                            }
                        });
            }
        };

        // If Billing client was disconnected, we retry 1 time and if success, execute the query
        startServiceConnectionIfNeeded(executeOnConnectedService);
    }

    public List<String> getSkus(@SkuType String type) {
        return SKUS.get(type);
    } 
    //start the purchase.
    public void startPurchaseFlow(final String skuId, final String billingType) {
        // Specify a runnable to start when connection to Billing client is established
        Runnable executeOnConnectedService = new Runnable() {
            @Override
            public void run() {
                BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
                        .setType(billingType)
                        .setSku(skuId)
                        .build();
                mBillingClient.launchBillingFlow(mActivity, billingFlowParams);
            }
        };

        // If Billing client was disconnected, we retry 1 time and if success, execute the query
        startServiceConnectionIfNeeded(executeOnConnectedService);
    }

    /*
     * We shoould check purchased products and update them accordingly...
     * in my case, i am trying to consume as if they are not consumed...
     */
    public void checkPurchasedProducts(String billingType) {
        mBillingClient.queryPurchaseHistoryAsync(billingType,
                new PurchaseHistoryResponseListener() {
                    @Override
                    public void onPurchaseHistoryResponse(@BillingResponse int responseCode,
                                                          List<Purchase> purchasesList) {
                        if (responseCode == BillingResponse.OK
                                && purchasesList != null) {
//                            for (Purchase purchase : purchasesList) {
//                                // Process the result.
//                            }
                            consumePurchases(purchasesList);
                            Log.d(TAG, "onPurchaseHistoryResponse: purchased items..");
                        }
                    }
                });

    }

    public void destroy() {
        mBillingClient.endConnection();
    }
}

The checkPurchasedProducts() will return you the history of purchased products. You can do your logic implementation (such as update local UI/database, disable purchase button…etc) after it is received.

The querySkuDetailsAsync() method will return all the products available for sale, in the google play console.

 

Lets create an interface to access the Billing Manager class.

package com.appsgit.inappexample.billing;

/**
 * An interface that provides an access to Billing Library methods
 */
public interface BillingProvider {
    BillingManager getBillingManager();
}

 

Lets create a model class before everything. Which contains data about In-App product.

package com.appsgit.inappexample.skulist.row;

/**
 * A model for SkusAdapter's row which holds all the data to render UI
 */
public class SkuRowData {
    private final String sku, title, price, description, billingType;

    public SkuRowData(String sku, String title, String price, String description, String type) {
        this.sku = sku;
        this.title = title;
        this.price = price;
        this.description = description;
        this.billingType = type;
    }

    public String getSku() {
        return sku;
    }

    public String getTitle() {
        return title;
    }

    public String getPrice() {
        return price;
    }

    public String getDescription() {
        return description;
    }

    public String getBillingType() {
        return billingType;
    }
}

 

Now lets move into MainActivity class,

package com.appsgit.inappexample;

import android.app.AlertDialog;
import android.os.Bundle;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.annotation.UiThread;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import android.widget.TextView;

import com.android.billingclient.api.Purchase;
import com.appsgit.inappexample.billing.BillingManager;
import com.appsgit.inappexample.billing.BillingProvider;
import com.appsgit.inappexample.skulist.PurchaseListFragment;

import java.util.List;

/**
 * Example App using Play Billing library.
 * */
public class MainActivity extends FragmentActivity implements BillingProvider {
    // Debug tag, for logging
    private static final String TAG = "MainActivity";

    // Tag for a dialog that allows us to find it when screen was rotated
    private static final String DIALOG_TAG = "dialog";

    private BillingManager mBillingManager;
    private PurchaseListFragment mPurchaseListFragment;
    private View mScreenWait, mScreenMain;

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

        setContentView(R.layout.activity_main);

        // Try to restore dialog fragment if we were showing it prior to screen rotation
        if (savedInstanceState != null) {
            mPurchaseListFragment = (PurchaseListFragment) getSupportFragmentManager()
                    .findFragmentByTag(DIALOG_TAG);
        }

        // Create and initialize BillingManager which talks to BillingLibrary
        mBillingManager = new BillingManager(this);

        mScreenWait = findViewById(R.id.screen_wait);
        mScreenMain = findViewById(R.id.screen_main);

        findViewById(R.id.button_purchase).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onPurchaseButtonClicked(view);
            }
        });

        showRefreshedUi();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBillingManager.destroy();
    }

    @Override
    public BillingManager getBillingManager() {
        return mBillingManager;
    }

    /**
     * User clicked purchased button - show a purchase dialog with all available SKUs
     */
    public void onPurchaseButtonClicked(final View arg0) {

        if (mPurchaseListFragment == null) {
            mPurchaseListFragment = new PurchaseListFragment();
        }

        if (!isPurchaseListFragmentShown()) {
            mPurchaseListFragment.show(getSupportFragmentManager(), DIALOG_TAG);
        }
    }

    /**
     * Remove loading spinner and refresh the UI
     */
    public void showRefreshedUi() {
        setWaitScreen(false);

        if (isPurchaseListFragmentShown()) {
            mPurchaseListFragment.refreshUI();
        }
    }

    /**
     * Show an alert dialog to the user
     * @param messageId String id to display inside the alert dialog
     */
    @UiThread
    void alert(@StringRes int messageId) {
        alert(messageId, null);
    }

    /**
     * Show an alert dialog to the user
     * @param messageId String id to display inside the alert dialog
     * @param optionalParam Optional attribute for the string
     */
    @UiThread
    void alert(@StringRes int messageId, @Nullable Object optionalParam) {
        if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
            throw new RuntimeException("Dialog could be shown only from the main thread");
        }

        AlertDialog.Builder bld = new AlertDialog.Builder(this);
        bld.setNeutralButton("OK", null);

        if (optionalParam == null) {
            bld.setMessage(messageId);
        } else {
            bld.setMessage(getResources().getString(messageId, optionalParam));
        }

        bld.create().show();
    }

    /**
     * Enables or disables the "please wait" screen.
     */
    private void setWaitScreen(boolean set) {
        mScreenMain.setVisibility(set ? View.GONE : View.VISIBLE);
        mScreenWait.setVisibility(set ? View.VISIBLE : View.GONE);
    }

    /**
     * Update UI to reflect model
     */
    @UiThread
    public void updateUi(List<Purchase> purchases) {
        String text = "";

        if (purchases != null && purchases.size() > 0) {
            for (Purchase purchase : purchases) {
                text += "Purchased Item : " + purchase.getSku() + "\n";
            }
        }

        ((TextView)findViewById(R.id.resultText)).setText(text);

        if (isPurchaseListFragmentShown()) {
            FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            ft.remove(mPurchaseListFragment);
            ft.commitAllowingStateLoss();
        }
    }

    public boolean isPurchaseListFragmentShown() {
        return mPurchaseListFragment != null && mPurchaseListFragment.isVisible();
    }
}

 

activity_main.xml is,

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="match_parent">

    <include layout="@layout/loading_indicator"/>

    <LinearLayout
        android:id="@+id/screen_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

        <Button
            android:id="@+id/button_purchase"
            style="@style/ButtonStyle"
            android:layout_gravity="center"
            android:text="@string/button_purchase" />

        <TextView
            android:id="@+id/resultText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:paddingTop="20dp"
            android:textSize="20sp"
            android:textColor="@color/gold"
            />

    </LinearLayout>
</FrameLayout>

 

Next we will create the DialogFragment to show the list of in-app product.

package com.appsgit.inappexample.skulist;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.android.billingclient.api.BillingClient.SkuType;
import com.android.billingclient.api.BillingClient.BillingResponse;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.appsgit.inappexample.R;
import com.appsgit.inappexample.billing.BillingProvider;
import com.appsgit.inappexample.skulist.row.SkuRowData;

import java.util.ArrayList;
import java.util.List;


public class PurchaseListFragment extends DialogFragment {
    private static final String TAG = "PurchaseListFragment";

    private RecyclerView mRecyclerView;
    private SkusAdapter mAdapter;
    private View mLoadingView;
    private TextView mErrorTextView;
    private BillingProvider mBillingProvider;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.purchase_list_fragment, container, false);
        mErrorTextView = (TextView) root.findViewById(R.id.error_textview);
        mRecyclerView = (RecyclerView) root.findViewById(R.id.list);
        mLoadingView = root.findViewById(R.id.screen_wait);
        // Setup a toolbar for this fragment
        Toolbar toolbar = (Toolbar) root.findViewById(R.id.toolbar);
        toolbar.setNavigationIcon(R.drawable.ic_launcher);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });
        toolbar.setTitle(R.string.button_purchase);
        setWaitScreen(true);

        mBillingProvider = (BillingProvider) getActivity();
        if (mRecyclerView != null) {
            mAdapter = new SkusAdapter(mBillingProvider);
            if (mRecyclerView.getAdapter() == null) {
                mRecyclerView.setAdapter(mAdapter);
                mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
            }
            //check for the products user purchased before load..
            mBillingProvider.getBillingManager().checkPurchasedProducts(SkuType.INAPP);
            loadUiData();
        }

        return root;
    }

    /**
     * Refreshes this fragment's UI
     */
    public void refreshUI() {

        if (mAdapter != null) {
            mAdapter.notifyDataSetChanged();
        }
    }

    /**
     * Enables or disables "please wait" screen.
     */
    private void setWaitScreen(boolean set) {
        mRecyclerView.setVisibility(set ? View.GONE : View.VISIBLE);
        mLoadingView.setVisibility(set ? View.VISIBLE : View.GONE);
    }

    /**
     * Executes query for SKU details at the background thread
     */
    private void loadUiData() {
        final List<SkuRowData> inList = new ArrayList<>();
        SkuDetailsResponseListener responseListener = new SkuDetailsResponseListener() {
            @Override
            public void onSkuDetailsResponse(int responseCode,
                    List<SkuDetails> skuDetailsList) {
                // If we successfully got SKUs, add a header in front of it
                if (responseCode == BillingResponse.OK && skuDetailsList != null) {
                    // Repacking the result for an adapter
                    for (SkuDetails details : skuDetailsList) {
                        Log.i(TAG, "Found sku: " + details);
                        inList.add(new SkuRowData(details.getSku(), details.getTitle(),
                                details.getPrice(), details.getDescription(),
                                details.getType()));
                    }
                    if (inList.size() == 0) {
                        displayAnErrorIfNeeded();
                    } else {
                        mAdapter.updateData(inList);
                        setWaitScreen(false);
                    }
                }
            }
        };

        // Start querying for in-app SKUs
        List<String> skus = mBillingProvider.getBillingManager().getSkus(SkuType.INAPP);
        mBillingProvider.getBillingManager().querySkuDetailsAsync(SkuType.INAPP, skus, responseListener);
        // Start querying for subscriptions SKUs
        skus = mBillingProvider.getBillingManager().getSkus(SkuType.SUBS);
        mBillingProvider.getBillingManager().querySkuDetailsAsync(SkuType.SUBS, skus, responseListener);

    }

    private void displayAnErrorIfNeeded() {
        if (getActivity() == null || getActivity().isFinishing()) {
            Log.i(TAG, "No need to show an error - activity is finishing already");
            return;
        }

        mLoadingView.setVisibility(View.GONE);
        mErrorTextView.setVisibility(View.VISIBLE);
        mErrorTextView.setText(getText(R.string.error_appsgit_not_finished));

        // TODO: Here you will need to handle various respond codes from BillingManager
    }
}

xml file for the purchase_list_fragment is,

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:app="http://schemas.android.com/apk/res-auto"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:background="@color/list_bg_color">

    <include layout="@layout/loading_indicator" />

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:background="@color/primary_color"
        android:layout_gravity="top"
        android:minHeight="?attr/actionBarSize"
        style="@style/ToolbarStyle"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Dark"/>

    <TextView
        android:id="@+id/error_textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:padding="@dimen/single_padding"
        android:visibility="gone" />

    <android.support.v7.widget.RecyclerView
        android:layout_marginTop="?attr/actionBarSize"
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center" />

</FrameLayout>

 

I have added Recycleviewer to show the list. I will add the related classes here,

Adapter class is,

//SkusAdapter Recyclerview adapter class
package com.appsgit.inappexample.skulist;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.appsgit.inappexample.R;
import com.appsgit.inappexample.billing.BillingProvider;
import com.appsgit.inappexample.skulist.row.RowViewHolder;
import com.appsgit.inappexample.skulist.row.SkuRowData;

import java.util.List;

/**
 * Adapter for a RecyclerView that shows SKU details for the app.
 */
public class SkusAdapter extends RecyclerView.Adapter<RowViewHolder>
        implements RowViewHolder.OnButtonClickListener {
    private List<SkuRowData> mListData;
    private BillingProvider mBillingProvider;

    public SkusAdapter(BillingProvider billingProvider) {
        mBillingProvider = billingProvider;
    }

    void updateData(List<SkuRowData> data) {
        mListData = data;
        notifyDataSetChanged();
    }

    @Override
    public RowViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View item = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.sku_details_row, parent, false);
        return new RowViewHolder(item, this);
    }

    @Override
    public void onBindViewHolder(RowViewHolder holder, int position) {
        SkuRowData data = getData(position);
        if (data != null) {
            holder.title.setText(data.getTitle());
            holder.description.setText(data.getDescription());
            holder.button.setEnabled(true);
            holder.button.setText("BUY (" + data.getPrice() + ")");
        }
        switch (data.getSku()) {
            case "theme1":
                holder.skuIcon.setImageResource(R.drawable.theme1_icon);
                break;
            case "theme2":
                holder.skuIcon.setImageResource(R.drawable.theme2_icon);
                break;
            case "subscription_monthly":
                holder.skuIcon.setImageResource(R.drawable.monthly_subscription_icon);
                break;
            case "subscription_yearly":
                holder.skuIcon.setImageResource(R.drawable.yearly_subscription_icon);
                break;
        }
    }

    @Override
    public int getItemCount() {
        return mListData == null ? 0 : mListData.size();
    }

    @Override
    public void onButtonClicked(int position) {
        SkuRowData data = getData(position);
        mBillingProvider.getBillingManager().startPurchaseFlow(data.getSku(),
                data.getBillingType());

    }

    private SkuRowData getData(int position) {
        return mListData == null ? null : mListData.get(position);
    }
}

 

RecyclerView.ViewHolder is here,

//RowViewHolder.java is RecyclerView.ViewHolder class
package com.appsgit.inappexample.skulist.row;

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import com.appsgit.inappexample.R;

/**
 * ViewHolder for quick access to row's views
 */
public final class RowViewHolder extends RecyclerView.ViewHolder {
    public TextView title, description;
    public Button button;
    public ImageView skuIcon;

    /**
     * Handler for a button click on particular row
     */
    public interface OnButtonClickListener {
        void onButtonClicked(int position);
    }

    public RowViewHolder(final View itemView, final OnButtonClickListener clickListener) {
        super(itemView);
        title = (TextView) itemView.findViewById(R.id.title);
        description = (TextView) itemView.findViewById(R.id.description);
        skuIcon = (ImageView) itemView.findViewById(R.id.sku_icon);
        button = (Button) itemView.findViewById(R.id.state_button);
        if (button != null) {
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    clickListener.onButtonClicked(getAdapterPosition());
                }
            });
        }
    }
}

 

RecyclerView ViewHolder xml UI file is here,

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/card_view"
    style="@style/CardViewStyle"
    android:layout_height="wrap_content"
    app:cardBackgroundColor="@color/row_bg_color">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        android:background="?attr/selectableItemBackground"
        android:padding="@dimen/max_padding">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/title"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:ellipsize="end"
                android:maxLines="1"
                android:textSize="16sp"
                android:textStyle="bold"/>

        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="horizontal"
            android:layout_marginTop="@dimen/single_padding">

            <ImageView
                android:id="@+id/sku_icon"
                android:gravity="start"
                android:layout_width="150dp"
                android:layout_height="wrap_content"/>

            <TextView
                android:id="@+id/description"
                android:layout_marginStart="@dimen/single_padding"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:textSize="15sp"
                android:layout_weight="1"/>
        </LinearLayout>

      <Button
            android:id="@+id/state_button"
            style="@style/ButtonStyle"
            android:text="@string/button_buy"
            android:layout_marginTop="@dimen/single_padding"
            android:contentDescription="@string/button_buy"/>

    </LinearLayout>
</android.support.v7.widget.CardView>

 

strings.xml file contains necessary string for the UI

 

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">AppsGit Billing example</string>
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">Trivial Drive v2</string>

    <string name="alert_already_purchased">Item is already purchased</string>

    <string name="error_no_skus">We didn\'t find any SKUs to display&#8230; Check your internet
        connection and make sure your Google Developer Console was setup correctly.</string>
    <string name="error_billing_unavailable">Billing unavailable. Make sure your Google Play app
        is setup correctly</string>
    <string name="error_billing_default">Billing unavailable. Please check your device.</string>
    <string name="error_appsgit_not_finished">TODO: Here you will need to perform the
        integration with Play Store to study the New way of integration with Play Billing.
    </string>

    <string name="button_purchase">Purchase Items</string>
    <string name="button_buy">Buy</string>

    <string name="content_description_app_loading_image">App loading image</string>
    <string name="content_desc_free_vs_premium_indicator">Free or Premium Image Indicator</string>
</resources>

I have attached almost all the files, I have added some images in drawable . you should replace it with your’s or else download them from the git repo.

Lets build the project and run.

 

Create Signed APK

After you done with your coding, create signed version of you APK.

Create An application in Google play Console

Now create an application in google play store console.  and upload it as a Alpha Release.

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

You should upload the APK as an alpha release. Because your app is not ready for public.

You can grant testing permission to only selected testers using Alpha testing.  check my blog post about how to do Alpha testing.

 

Create list of testers in Google play console

Goto Console -> Settings -> Manage Testers and create a list of testers.

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

You should now publish your app to the alpha release.

 

Note :- Earlier you could be able to create in-app products if the APK is in the draft mode. But Draft Apps are No Longer Supported

 

Setting up your product list on the Google Play Console

Your App is now published to Alpha release. Now goto Application -> Store presence -> In-app products   you should be able to create  in-app products.

Create two in-app products and two subscriptions, like below.

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

Now you have created 4 products. Lets publish the app and wait for about 4 – 5 hours until the changes take place.

 

 

 

After the app is published,  go to console -> application -> Release Management -> App release and click on “MANAGE ALPHA” button and assign testers list you have previously created for the app. (If you don’t assign, testers will not be able to download the app.)

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

Share the opt-in URL. and let testers open the app.

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

Testers can open the app now.

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

 

Now you can see there are 4 products in the list.

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

If you click the BUY button, you will be redirected to next screen.

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

If you press continue button you will be redirected to

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

 

Create Authorized google test accounts and test in-App billing/purchase 

The problem above is, you have to use credit card to do the test purchase.  This is not a good approch. To solve this issue. We have to create licensed testers.  they will be able to do test purchase using fake credit card.

To create license testers, goto Console -> Settings  if you Account Details tab, You can see “Licence testing” panel.

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

 

After 15 to 30 minutes. try to but the in-app product in the app. you should be able to purchase now without any issues.

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

After you successfully bought the in-App product. you should see the following update on the screen.

 

Android In App Billing API Buy Subscribe Monetize app Play billing library

 

In-App billing Error Handling 

I will list out few error handling which occured during the my development period.

W/BillingClient: Unable to buy item, Error response code: 7

The above error describe you that you are trying to purchase the product again before consume it. So try to consume it before purchase again.

/*
* By default you cannot buy the same product again and again...after you purchased the item you should cosume it.
* For testing purpose i am going to consume all the product each and everytim i buy...
 */
public void consumePurchases(List<Purchase> purchases) {
    try {
        if (purchases != null && purchases.size() > 0) {
            for (final Purchase purchase : purchases) {
                if (purchase.getPurchaseToken() != null && !purchase.getPurchaseToken().isEmpty()) {
                    mBillingClient.consumeAsync(purchase.getPurchaseToken(), new ConsumeResponseListener() {
                        @Override
                        public void onConsumeResponse(int responseCode, String purchaseToken) {
                            Log.d(TAG, "onConsumeResponse: purchased SKU  " + purchase.getSku());
                            Log.d(TAG, "onConsumeResponse: purchased getPurchaseToken  " + purchase.getPurchaseToken());
                        }
                    });
                }
            }
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

 

W/BillingClient: Unable to buy item, Error response code: 8

It will occur when you try to consume a product which is already consumed product.

 

 

 

Thats all my folks, You can check the source code in my Github account. feel free to use them in your project.

 

 

I know the post is bit lengthy.! If i have missed anything, please let me know via comment. I will definitely have look at it.

 

cheers..!

 

PLEASE DON”T COPY SCREENSHOTS OR IMAGES.

 

About Zumry

Zumry Mohamed

Self Taught iOS & Android Mobile Application Developer.

Article written by zumrywahid

Self Taught iOS & Android Mobile Application Developer.

This Article Has 1 Comment
  1. Damien Reply

    Thank you very much for an excellent tutorial, that shows both the App side and Google Play side of the process. Keep up the good work.

Leave a Reply

Your email address will not be published. Required fields are marked *