Friday, March 22, 2019

Understand Android TrivialDrive_v2 sample project

0. TrivialDriveActivity extends BaseGamePlayActivity
This class is the root activity of the app, and it only sets the layout resource id.

1. BaseGamePlayActivity extends FragmentActivity implements BillingProvider
Main class implements the application's logic. 

The main members include 
MainViewController mViewController
BillingManager mBillingManager

The methods in the BillingProvider interface is implemented through the MainViewController's method with the same name. BillingProvider interface is used by application to communicate with billing library  

public interface BillingProvider
    BillingManager getBillingManager(); 
    boolean isPremiumPurchased(); 
    boolean isGoldMonthlySubscribed(); 
    boolean isTankFull(); 
    boolean isGoldYearlySubscribed(); 


When user clicks purchase button, it shows the UI AcquireFragment (derived from DialogFragment) implemented in sku (stock keeping unit) skulist folder, and then allow user to purchase the product.

When user clicks drive button, it will reduce the count for the tank, and show it on UI.

showRefreshUI and updateUI methods are used to update activity image based on current purchase state. It retrieves the purchase state from MainViewController and then reflects the state in UI elements.

1. 1 Public class MainViewController 
The controller class between UI Activity (BaseGamePlayActivity mActivity) and BillingLibrary (UpdateListener mUpdateListener), its data members hold the state of the purchase. MainViewController mainly handles UI related logic.

It has date member of
private final UpdateListener mUpdateListener;  
private BaseGamePlayActivity mActivity;
mUpdateListener is implemented as a inner class, its main function is passing the purchasing information to mActivity.
The data member int mTank defined in MainViewController is used to track how much gas the car has, when user clicks drive button, it will reduce by 1. User can click purchase button to purchase more gas.

The data member of mGoldMonthly and mGoldYearly keep the purchase status of subscription.

1.1.1 private class UpdateListener implements BillingUpdateListener
UpdateListener implements BillingUpdateListener interface, which is defined in BillingManager class. It allows mainViewController to get purchase notification from BillingManager, such as client setup finished, purchase state updated, etc

public interface BillingUpdatesListener {     
void onBillingClientSetupFinished();     
void onConsumeFinished(String token, @BillingResponse int result);     
void onPurchasesUpdated(List<Purchase> purchases);
}

1.2 public class BillingManager implements PurchaseUpdateListener
BillingManager mainly handled purchase logic by calling Android Bill APIs

It has the data member of
    private BillingClient mBillingClient;
    private final Activity mActivity;  //from constructor parameter
    BillingUpdatesListener mBillingUpdatesListener;  //from constructor parameter

BillingManager implements Android PurchaseUpdateListener interface, which is the main interface for application to handle just happened inapp purchase result through its single method of
onPurchasesUpdated(int responseCode, List<Purchase> purchases)
This method is called for both purchases initiated by your app and the one initiated by play store, such as subscription auto renew.
onPurchaseUpdated is also called when app starts and calls the queryPurchases method to get the local cached purchased items, so onPurchaseUpdated method can initialize the application state based on the purchase information.

In BillingManager constructor, it first creates BillingClient, and set the listener of PurchaseUpdateListener to itself.

BillingManager has a data member of mBillingUpdateListener, which is implemented by MainViewController, this data member is used by BillingManager to notify MainViewController of purchase activity.

When app starts, billingManager calls BillingClient.startConnection method with a listener to tell BillingUpdateListener.onBillingClientSetupFinished that the connection to billing service is ready. Then it calls BillingManager.queryPurchases(), which calls BillingClient.queryPurchases to get PurchaseResult info. If the device has purchased any product, then BillingManager calls handlePurchase to process the result, and also call MainViewController's onPurchasesUpdated method to update the UI.

BillingManager's querySkuDetailsAsync method uses BillingClient.querySkuDetailsAsync method to get details of each SKU item, and sends back the result to AcquireFragment.

BillingManager has a data member of mPurchases, which contains a list of purchased items

2. UI items for purchasing 

2.1 AcquireFragment
When user clicks purchase button, the app shows AcquireFragment,  in its onCreateView method, it calls handleManagerAndUIReady method, which calls querySkuDetails() method.
AcquireFragment's addSkuRows method calls BillingManager's querySkuDetailsAsync method to get each SKU's detailed information from play store, and return it as an SkuDetails list. The list is then used as adapter for AcquireFragment to provide the recycleView's data.

AcquireFragment has a data member of BillingManager to get purchase information from play store.

When user clicks the buy button from the inapp purchase item list, the corresponding uiFactoryDelegate's onButtonClicked method is called to start the purchase process. It does so by calling BillingManager's initiatePurchaseFlow method, which calls BillingClient's launchBillingFlow method to start the actual purchase.

When purchase finished, billingManager's onPurchasesUpdated method adds the purchased item into mPurchases list, and then calls .onPurchasesUpdated method to notify mainViewController to update the UI based on the new purchase state.

2.2 SkusAdapter
SkusAdapter used by AcquireFragment to manage RowViewHolder list item.

2.3 UIDelegatesFactory
UIDelegateFactory holds a dictionary, the key is string of each SKU IDs defined in Google Play Console's inapp products page, the value is the an delegate class which handles how to bind view for each type of purchase item. Those SKU IDS are items that can be purchased by user.

2.4 RowViewHolder
RowViewHolder holds a reference to each purchase view item's elements, so it can easy access the view item. When user clicks buy button to purchase an item, it receives the button clicks event and calls into UIManager to handle it.

2.5 UIManager
UIManager gets the SkuRowData item based on view position, and then it calls UIDelegateFactory's to invoke the view item's onButtonClicked method.

3. Application handles inapp purchase state 
Usually, when app starts it calls queryPurchase to get the already purchased items, and then applies the result to onPurchaseUpdated method to update the app state based on purchase result.

When a new purchase happens by calling BillingClient.launchBillingFlow, the purchase result will be reported by onPurchaseUpdate listener set to BillingClient instance. The app state will be updated based on new purchase result.

As a result, when app is just installed on a device, after it is starts, it should call queryPurchase first to try to get the cached purchase result. If current purchase is available, then app should give user permission and continue.

The local purchase history is stored in Play Store app, not in the application itself's data storage, so uninstall the application will not remove the purchase history from the device.

Regarding the issue of user cancelling subscription, the application does not need to check or calculate when auto renew subscription is cancelled by user and expired. Instead application only needs to check the returned purchase history from BillingManger.queryPurchases method, if a purchase item is returned from the method it indicates a valid subscription still exists, and user should get the required permission. If no purchase item is returned, then the subscription is expired, and the app should not give user the required permission.

The application should only handle three purchase states:
1. Not subscribed. Disable permission to protected resource, and also prompt to subscribe.
2. Subscribed with auto renew of true. Allow user to access the protected resource.
3. Subscribed with auto renew of false. Current subscription is still valid, but user cancelled the subscription or credit card information is expired. Allow user to access the protected resource, and also prompt user to restore the subscription and check the credit card information.