Among all HMS services, ML Kit, Cloud DB, and Cloud Storage are the most commonly applied services in educational apps.
This codelab provides an overview for:

This codelab walks you through how to integrate Cloud Storage, Cloud DB, ML Kit, and In-App Purchases (IAP).
Cloud Storage is used to save PDF files to the cloud, as well as provide links to access them.
Cloud DB is used to store user information in the database, and allows for data to be created, read, updated, and deleted from any device.
ML Kit is used for developing the translation and TTS function.

Application Scenario

This codelab illustrates the process for developing an e-book reader app, showing how HMS services can be integrated into your app with ease, and how they can help boost your revenue.
Users of E-Book Reader can use it to register for an account, read books, translate books into other languages, and convert text into speech, which can be listened to. In addition, users can receive notifications about topics they've subscribed to, and enjoy ad-free usage with IAP.
In this codelab, you will learn how to develop an e-book reader app which can:

Data used in the project is obtained entirely from the cloud database in real time.
Please note that Cloud Storage, Cloud DB, ML Kit, and IAP are closely related to each other:
A PDF file is firstly saved via Cloud Storage. Its path is then saved by Cloud DB. Cloud DB is used to obtain details about books and display books within the app, where users are free to read them or have the translated, with the help of ML Kit. IAP then allows the app to offer membership plans for users who would prefer a premium, ad-free reading experience.

What You Will Create

In this codelab, you will use the demo project that we provided to call the mentioned services. With the demo project, you will:

Activity Description

Hardware Requirements

Software Requirements

To integrate HMS services, you'll need to complete the following tasks:

For more details, see Preparations for Integrating HUAWEI HMS Core.

Enabling HMS Services

Sign in to AppGallery Connect, click My projects, and select a project. On the page displayed, go to Project settings > Manage APIs. Toggle on the switches of the following services to enable them:

Now, the services of HMS required for developing E-book Reader have been enabled.

Integrating Cloud Storage

After a PDF file is uploaded to Cloud Storage, its path can be accessed from any device.
To store PDF files in Cloud Storage, follow the steps below:
Step 1. Upload files to Cloud Storage in AppGallery Connect.

Step 2. Click View details. Then, click Copy to copy the sharing token. Save this sharing token to Cloud DB.

—End

Integrating Cloud DB

To store data in Cloud DB, first create an object type in AppGallery Connect. You can create and modify object types in a Cloud DB zone. In addition, you can also export object types from or import object types into a Cloud DB zone.
Note: First integrate the anonymous account authentication mode of Auth Service into the app to authenticate users who attempt to access Cloud DB.
To find Cloud DB, sign in to AppGallery Connect, click My projects, and select a project. The service is displayed in the left navigation pane under Build.

Adding a Cloud DB Zone

A Cloud DB zone is used to store data on the cloud, which can be created in AppGallery Connect. To create one, follow the steps below:
Step 1. Click the Cloud DB Zones tab.
Step 2. Click Add.
Step 3. On the page displayed, enter the name for the Cloud DB zone, and then click OK.
Step 4. Find the Cloud DB zone you've created from the Cloud DB zone list.
—End

Adding and Exporting Object Types

Step 1. Sign in to AppGallery Connect, click My projects, and select a project. On the page displayed, go to Build > Cloud DB.
Step 2. Under the ObjectTypes tab, click Add.

Step 3. On the page displayed, enter the name for the object type, and click Next.
Step 4. Click to add fields for an object type. Click OK.

Step 5. Find the created object type in the object type list. If you need to export the object type, click Export.


Step 6. Select JAVA as the format of the exported file. Enter its package name, and click Export.

—End

Adding the Object Type File to Your Project and Initializing the Cloud DB Zone

Add the exported files of the object type to your project:

Opening a Cloud DB Zone

Step 1. Initialize the Cloud DB zone in your app.
Java:

public class EBookApplication extends Application { @Override public void onCreate() { super.onCreate(); initAGConnectCloudDB(this); } /** * Initialize AGConnectCloudDB in the app. * * @param context app context. */ public static void initAGConnectCloudDB(Context context) { AGConnectCloudDB.initialize(context); } }

Kotlin:

class EBookApplication : Application() { override fun onCreate() { super.onCreate() initAGConnectCloudDB(this) } companion object { /** * Initialize AGConnectCloudDB in the app. * * @param context app context. */ fun initAGConnectCloudDB(context: Context?) { AGConnectCloudDB.initialize(context!!) } }

Step 2. Obtain the AGConnectCloudDB instance and create object types.
Java:

public static synchronized CloudDb getInstance() { if (sCloudDb == null) { sCloudDb = new CloudDb(); sAgconnectCloudDb = AGConnectCloudDB.getInstance(); } return sCloudDb; } /** * Call AGConnectCloudDB.createObjectType to initialize the schema. */ public void createObjectType() { try { sAgconnectCloudDb.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo()); } catch (AGConnectCloudDBException exception) { Log.e(TAG, "createObjectType: " + exception.getMessage()); } }

Kotlin:
fun createObjectType() {

fun createObjectType() { try { if (sAgconnectCloudDb != null) sAgconnectCloudDb!!.createObjectType( ObjectTypeInfoHelper.getObjectTypeInfo() ) } catch (exception: AGConnectCloudDBException) { Log.e(TAG, "AGConnectCloudDBException") } }

Step 3. Open the Cloud DB zone.
Java:

/** * Call AGConnectCloudDB.openCloudDBZone to open a Cloud DB zone. * It is set in cloud cache mode. Data can also be stored in local storage. * * @param dialogInterface to obtain the callback from the Cloud DB zone */ public void openCloudDBZone(DBOpenInterface dialogInterface) { CloudDBZoneConfig mConfig = new CloudDBZoneConfig( CloudDbConstant.DB_NAME, CloudDbConstant.SYNC_PROPERTY, CloudDbConstant.ACCESS_ZONE); mConfig.setPersistenceEnabled(true); Task openDBZoneTask = sAgconnectCloudDb.openCloudDBZone2(mConfig, true); openDBZoneTask .addOnSuccessListener( cloudDbZone -> { mCloudDBZone = cloudDbZone; dialogInterface.success(cloudDbZone); }) .addOnFailureListener( exception -> { dialogInterface.failure(); }); }

Kotlin:

fun openCloudDBZone(dialogInterface: DBOpenInterface) { val mConfig = CloudDBZoneConfig( CloudDbConstant.DB_NAME, CloudDbConstant.SYNC_PROPERTY, CloudDbConstant.ACCESS_ZONE ) mConfig.persistenceEnabled = true if (sAgconnectCloudDb != null) { val openDBZoneTask = sAgconnectCloudDb!!.openCloudDBZone2(mConfig, true) openDBZoneTask .addOnSuccessListener { cloudDbZone: CloudDBZone? -> cloudDBZone = cloudDbZone dialogInterface.success(cloudDbZone) } .addOnFailureListener { exception: Exception? -> dialogInterface.failure() } } }

—End

Saving Data in Cloud DB

Save the bookmark in Cloud DB to store the page as a user favorite.
Java:

/** * Save the bookmark in Cloud DB. * * @param pageNo which is marked as a bookmark. */ private void saveBookmark(int pageNo) { Bookmark mBookmark = new Bookmark(); mBookmark.setEmailid(sp.fetchEmailId()); mBookmark.setPageno(pageNo); if (bookmarkMaxId > 0) { bookmarkMaxId++; mBookmark.setBookmarkid(bookmarkMaxId); } else { mBookmark.setBookmarkid(1); } mBookmark.setBookId(mCurrentBookId); mBookmark.setBookname(mBookName); if (mCloudDBZone == null) { return; } Task upsert = mCloudDBZone.executeUpsert(mBookmark); upsert .addOnSuccessListener( snapshot -> { btnBookmarkPage.setBackgroundResource(R.drawable.bookmark1); mCurrentBookDetails.add(currentPage.getIndex()); showToast("Page " + (currentPage.getIndex() + 1) + " added as Favourite.", getContext()); }) .addOnFailureListener( exception -> { showToast(getString(R.string.bookmark_error), getContext()); exception.printStackTrace(); }); }

Kotlin:

private fun saveBookmark(pageNo: Int) { val mBookmark = Bookmark() mBookmark.emailid = sp!!.fetchEmailId() mBookmark.pageno = pageNo if (bookmarkMaxId > 0) { bookmarkMaxId++ mBookmark.bookmarkid = bookmarkMaxId } else { mBookmark.bookmarkid = 1 } mBookmark.bookId = mCurrentBookId mBookmark.bookname = mBookName if (mCloudDBZone == null) { return } val upsert = mCloudDBZone!!.executeUpsert(mBookmark) upsert .addOnSuccessListener { snapshot: Int? -> Log.d(TAG, "Bookmark added successfully") btnBookmarkPage!!.setBackgroundResource(R.drawable.bookmark1) mCurrentBookDetails!!.add(currentPage!!.index) showToast( "Page " + (currentPage!!.index + 1) + " added as Favourite.", context ) } .addOnFailureListener { exception: Exception? -> showToast( getString(R.string.bookmark_error), context ) } }

Deleting Data from Cloud DB

Delete a bookmark.
Java:

Kotlin:

private fun deleteBookmark(bookmarkDetail: List) { val delete = mCloudDBZone!!.executeDelete(bookmarkDetail) delete.addOnSuccessListener { snapshot: Int? -> mCurrentBookDetails!!.removeAt(currentPage!!.index) btnBookmarkPage!!.setBackgroundResource(R.drawable.nobookmark1) } .addOnFailureListener { e: Exception? -> Log.d( TAG, "Exception failure deleteBookmark" ) } }

Obtaining Data from Cloud DB

Obtain the bookmark of a specific book.
Java:

private void deleteBookMark(List bookmrkInfoList, int pageNo) { int bukmarkid = Integer.parseInt(Integer.toString(mCurrentBookId) + Integer.toString(pageNo)); for (int i = 0; i < bookmrkInfoList.size(); i++) { if (!bookmrkInfoList.get(i).getBookmarkid().equals(bukmarkid)) { bookmrkInfoList.remove(i); } } if (mCloudDBZone == null) { return; } Task delete = mCloudDBZone.executeDelete(bookmrkInfoList); delete .addOnSuccessListener( new OnSuccessListener() { @Override public void onSuccess(Integer cloudDBZoneResult) { mCurrentBookDetails.remove(pageNumber); btnBookmarkPage.setBackgroundResource(R.drawable.nobookmark); } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception e) { showToast(getString(R.string.bookmark_delete_error), getContext()); } }); }
public void queryBookmarkList() { if (mCloudDBZone == null) { return; } Task> queryTask = mCloudDBZone.executeQuery( CloudDBZoneQuery.where(Bookmark.class), CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY); queryTask .addOnSuccessListener( new OnSuccessListener>() { @Override public void onSuccess(CloudDBZoneSnapshot snapshot) { Log.d(TAG, "queryBookmarkList success"); processQueryResult(snapshot); } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception exp) { Log.d(TAG, exp.getMessage()); showToast(exp.getMessage(), getContext()); } }); }

Kotlin:

fun queryBookmarkList() { if (mCloudDBZone == null) { return } val queryTask = mCloudDBZone!!.executeQuery( CloudDBZoneQuery.where(Bookmark::class.java) .equalTo("emailid", sp!!.fetchEmailId()), CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY ) queryTask .addOnSuccessListener { snapshot -> Log.d( TAG, "Get Bookmark list successfully" ) processQueryResult(snapshot) } .addOnFailureListener { showToast("on failure", context) } }

Integrating IAP

Configure the product information in AppGallery Connect.

  1. Sign in to AppGallery Connect and click My apps.
  2. Select the app for which you wish to add a product.
  3. Click the Operate tab. Go to Products and click Product Management in the left navigation pane. Go to Products > Add Product.
  4. Configure the product information and click Save.
  5. In the dialog box that is displayed, click OK. After the configuration is completed, the product will be in a valid state in the product list.

Displaying Product Information

Call the obtainProductInfo API to query the product configured in AppGallery Connect. After a successful call, obtain the product details by calling ProductInfoResult and display the details in RecyclerView.
Java:

private void queryProducts(List productIds) { IapRequestHelper.obtainProductInfo( Iap.getIapClient(view.getActivity()), productIds, IapClient.PriceType.IN_APP_SUBSCRIPTION, new IapApiCallback() { @Override public void onSuccess(final ProductInfoResult result) { if (result == null) { return; } List productInfos = result.getProductInfoList(); view.showProducts(productInfos); } @Override public void onFail(Exception exception) { int error = ExceptionHandle.handle(view.getActivity(), exception); if (errorcode != ExceptionHandle.SOLVED) { Log.e(TAG, "unknown error"); } view.showProducts(null); } }); }

Kotlin:

private fun queryProducts(productIds: List) { IapRequestHelper.obtainProductInfo( Iap.getIapClient(view.getActivity()), productIds, IapClient.PriceType.IN_APP_SUBSCRIPTION, object : IapApiCallback { fun onSuccess(result: ProductInfoResult) { if (result == null) { return } val productInfos = result.productInfoList view.showProducts(productInfos) } override fun onFail(exception: Exception) { val error = ExceptionHandle.handle(view.getActivity(), exception) if (errorcode !== ExceptionHandle.SOLVED) { Log.e(TAG, "unknown error") } view.showProducts(null) } }) }

Purchasing the Product

Step 1. Create a task for calling the createPurchaseIntent API.
Step 2. Set OnSuccessListener and OnFailureListener for the task to receive the API call result.
Java:

public void buy(final String productId) { cacheOwnedPurchasesResult = null; IapClient iapClient = Iap.getIapClient(view.getActivity()); IapRequestHelper.createPurchaseIntent( iapClient, productId, IapClient.PriceType.IN_APP_SUBSCRIPTION, new IapApiCallback() { @Override public void onSuccess(PurchaseIntentResult result) { if (result == null) { return; } // Pull up the page to complete the payment process. IapRequestHelper.startResolutionForResult( view.getActivity(), result.getStatus(), Constants.REQ_CODE_BUY); } @Override public void onFail(Exception exception) { int errorCode = ExceptionHandle.handle(view.getActivity(), exception); if ( errorCode != ExceptionHandle.SOLVED) { if (errorCode == OrderStatusCode.ORDER_PRODUCT_OWNED) { showSubscription(productId); } else { Log.e(TAG, "unknown error"); } } } }); }

Kotlin:

fun buy(productId: String?) { cacheOwnedPurchasesResult = null val iapClient = Iap.getIapClient(view.getActivity()) IapRequestHelper.createPurchaseIntent( iapClient, productId, IapClient.PriceType.IN_APP_SUBSCRIPTION, object : IapApiCallback { fun onSuccess(result: PurchaseIntentResult) { if (result == null) { return } // Pull up the page to complete the payment process. IapRequestHelper.startResolutionForResult( view.getActivity(), result.status, Constants.REQ_CODE_BUY ) } override fun onFail(exception: Exception) { val errorCode = ExceptionHandle.handle(view.getActivity(), exception) if (errorCode != ExceptionHandle.SOLVED) { if (errorCode == OrderStatusCode.ORDER_PRODUCT_OWNED) { showSubscription(productId) } else { Log.e(TAG, "unknown error") } } } }) }

Step 3. The callback indicating the payment result is indicated by onActivityResult. Use the parsePurchaseResultInfoFromIntent method of IapClient to obtain the PurchaseResultInfo object, which contains the payment result.

Java:

@Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); Log.d(TAG, "onActivityResult requestCode is:" + requestCode); if (requestCode == Constants.REQ_CODE_BUY) { if (resultCode == Activity.RESULT_OK) { Log.d(TAG, "onActivityResult RESULT_OK"); int purchaseResult = SubscriptionUtils.getPurchaseResult(getActivity(), data); if (purchaseResult == OrderStatusCode.ORDER_STATE_SUCCESS) { Log.d(TAG, "onActivityResult ORDER_STATE_SUCCESS"); Toast.makeText(getActivity(), R.string.pay_success, Toast.LENGTH_SHORT).show(); presenter.refreshSubscription(); return; } if (purchaseResult == OrderStatusCode.ORDER_STATE_CANCEL) { Log.d(TAG, "onActivityResult ORDER_STATE_CANCEL"); Toast.makeText(getActivity(), R.string.cancel, Toast.LENGTH_SHORT).show(); return; } Toast.makeText(getActivity(), R.string.pay_fail, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getActivity(), R.string.cancel, Toast.LENGTH_SHORT).show(); } } }

Kotlin:

fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) { super.onActivityResult(requestCode, resultCode, data) Log.d(TAG, "onActivityResult requestCode is:$requestCode") if (requestCode == Constants.REQ_CODE_BUY) { if (resultCode == Activity.RESULT_OK) { Log.d(TAG, "onActivityResult RESULT_OK") val purchaseResult = SubscriptionUtils.getPurchaseResult(getActivity(), data) if (purchaseResult == OrderStatusCode.ORDER_STATE_SUCCESS) { Log.d(TAG, "onActivityResult ORDER_STATE_SUCCESS") Toast.makeText(getActivity(), R.string.pay_success, Toast.LENGTH_SHORT).show() presenter.refreshSubscription() return } if (purchaseResult == OrderStatusCode.ORDER_STATE_CANCEL) { Log.d(TAG, "onActivityResult ORDER_STATE_CANCEL") Toast.makeText(getActivity(), R.string.cancel, Toast.LENGTH_SHORT).show() return } Toast.makeText(getActivity(), R.string.pay_fail, Toast.LENGTH_SHORT).show() } else { Toast.makeText(getActivity(), R.string.cancel, Toast.LENGTH_SHORT).show() } } }

—End

Integrating ML Kit

ML Kit helps with implementing TTS and translation into the app.

Implementing TTS

TTS can convert text information into audio output in real time. Follow the steps below to implement it:
Step 1. Initialize the API key when the app is started.
Java:

/** * Set the key, which is needed to use ML Kit. */ private void setApiKey() { AGConnectServicesConfig config = AGConnectServicesConfig.fromContext(getActivity()); MLApplication.getInstance().setApiKey(config.getString(API_KEY)); }

Kotlin:

/** * Set the key, which is needed to use ML Kit. */ private fun setApiKey() { val config = AGConnectServicesConfig.fromContext(getActivity()) MLApplication.getInstance().apiKey = config.getString(API_KEY) }

Step 2. Create the object of the MLTtsConfig class and pass the object to MLTtsEngine.

Java:

mlConfigs = new MLTtsConfig(); mlTtsEngine = new MLTtsEngine(mlConfigs);

Kotlin:

mlConfigs = MLTtsConfig() mlTtsEngine = MLTtsEngine(mlConfigs)

Step 3. Add the callback method of MLTtsEngine.

Java:

mlTtsEngine.setTtsCallback(new MLTtsCallback() { String str = ""; @Override public void onError(String taskId, MLTtsError err) { str = "TaskID: " + taskId + ", error:" + err; displayResult(str); } @Override public void onWarn(String taskId, MLTtsWarn mlTtsWarn) { str = "TaskID: " + taskId + ", warn:" + mlTtsWarn; displayResult(str); } @Override public void onRangeStart(String taskId, int start, int end) { str = "TaskID: " + taskId + ", onRangeStart [" + start + "," + end + "]"; displayResult(str); } @Override public void onAudioAvailable( String s, MLTtsAudioFragment mlTtsAudioFragment, int i, Pair pair, Bundle bundle) { } @Override public void onEvent(String taskId, int eventName, Bundle bundle) { str = "TaskID: " + taskId + ", eventName:" + eventName; if (eventName == MLTtsConstants.EVENT_PLAY_STOP) { str += " " + bundle.getBoolean(MLTtsConstants.EVENT_PLAY_STOP_INTERRUPTED); } displayResult(str); } });

Kotlin:

mlTtsEngine = MLTtsEngine(mlConfigs) mlTtsEngine!!.setTtsCallback(object : MLTtsCallback { var str = "" override fun onError(taskId: String, err: MLTtsError) { str = "TaskID: $taskId, error:$err" displayResult(str) } override fun onWarn(taskId: String, mlTtsWarn: MLTtsWarn) { str = "TaskID: $taskId, warn:$mlTtsWarn" displayResult(str) } override fun onRangeStart( taskId: String, start: Int, end: Int ) { str = "TaskID: $taskId, onRangeStart [$start,$end]" displayResult(str) } override fun onAudioAvailable( s: String, mlTtsAudioFragment: MLTtsAudioFragment, i: Int, pair: Pair, bundle: Bundle ) { } override fun onEvent( taskId: String, eventName: Int, bundle: Bundle ) { str = "TaskID: $taskId, eventName:$eventName" if (eventName == MLTtsConstants.EVENT_PLAY_STOP) { str += " " + bundle.getBoolean(MLTtsConstants.EVENT_PLAY_STOP_INTERRUPTED) } displayResult(str) } })

Step 4. Call the speak API of MLTtsEngine to start TTS.

Java:

mlTtsEngine.speak(text, MLTtsEngine.QUEUE_APPEND);

Kotlin:

val id = mlTtsEngine!!.speak(text, MLTtsEngine.QUEUE_APPEND)

Step 5. Call the stop API of MLTtsEngine to stop TTS.

mlTtsEngine.stop();

Step 6. Call the pause API of MLTtsEngine to pause TTS.

mlTtsEngine.pause();

Step 7. Call the resume API of MLTtsEngine to resume TTS.

mlTtsEngine.resume();

—End

Implementing Translation

Call the getRemoteTranslator API of MLTranslatorFactory. By using the code below, we can translate the text into Hindi, Chinese, and Russian.

Java:

private void remoteTranslator(String lang) { // Create an analyzer. You can customize the analyzer by creating an MLRemoteTranslateSetting object. MLRemoteTranslateSetting setting = new MLRemoteTranslateSetting.Factory().setTargetLangCode(lang).create(); this.translator = MLTranslatorFactory.getInstance().getRemoteTranslator(setting); String sourceText = readPdf(downloadedFilePath.getPath()); Task task = this.translator.asyncTranslate(sourceText); task.addOnSuccessListener( new OnSuccessListener() { @Override public void onSuccess(String text) { DisplayBook.this.remoteDisplaySuccess(text, true); }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception exception) { // Recognition failure. Log.d(TAG, exception.getLocalizedMessage()); DisplayBook.this.displayFailure(exception); } }); } }

Kotlin:

private fun remoteTranslator(lang: String) { setProgressBar(context) // Create an analyzer. You can customize the analyzer by creating an MLRemoteTranslateSetting object. val setting = MLRemoteTranslateSetting.Factory().setTargetLangCode(lang).create() translator = MLTranslatorFactory.getInstance().getRemoteTranslator(setting) val sourceText = readPdf(downloadedFilePath!!.path) if (sourceText != null) { val task = translator?.asyncTranslate(sourceText) task?.addOnSuccessListener { text -> stopProgressBar() remoteDisplaySuccess(text, true) } }?.addOnFailureListener { exception -> // Recognition failure. Log.d(TAG, "Exception : onfailure") displayFailure(exception) } } else { showToast("Nothing to translate", context) } }

Well done. You have successfully completed this codelab, built an e-book reader app, and learned how to:

For more information, click the following links:
ML Kit
Cloud DB
Cloud Storage
In-App Purchases
To download the sample code, click the button below:
Download

Code copied