Pygmy Collection Application Part 5 (Safety Detect) - Huawei Developers

Introduction​
In this article, we will learn how to integrate Huawei Safety Detect in Pygmy collection finance application.
If are you new to this application please follow my previous articles
Pygmy collection application Part 1 (Account kit)
Intermediate: Pygmy Collection Application Part 2 (Ads Kit)
Intermediate: Pygmy Collection Application Part 3 (Crash service)
Intermediate: Pygmy Collection Application Part 4 (Analytics Kit Custom Events)
Click to expand...
Click to collapse
What is Safety detect?
Safety Detect builds robust security capabilities, including system integrity check (SysIntegrity), app security check (AppsCheck), malicious URL check (URLCheck), fake user detection (UserDetect), and malicious Wi-Fi detection (WifiDetect), into your app, effectively protecting it against security threats.
Why do we need to use safety detect?
Mobile applications capture almost 90% of people’s time on mobile devices, while the rest of the time is spent browsing the web. So basically now a day’s mobile usage is more than the web. Since all the users are using smart mobile phones for daily needs like, reading news, email, online shopping, booking taxi, and wallets to pay, educational and Finance & Banking apps etc. Even for banking transaction there was time people use to stand in the queue to deposit or withdraw, but now everything can be done in mobile application with just 2 to 3 clicks. Since everything happening over the phone definitely we should provide security to mobile apps.
Now let’s see what all are the security features provided by Huawei Safety detect kit.
SysIntegrity
AppsCheck
URLCheck
UserDetect
WifiDetect
Integration of Safety detect
1. Configure application on the AGC.
2. Client application development process.
How to integrate Huawei Safety Detect in Android finance application?
To achieve this you need to follow couple of steps as follows.
1. Configure application on the AGC.
2. Client application development process.
Configure application on the AGC
Follow the steps.
Step 1: We need to register as a developeraccount in AppGallery Connect. If you are already developer ignore this step.
Step 2: Create an app by referring to Creating a Project and Creating an App in the Project
Step 3: Set the data storage location based on current location.
Step 4: Enabling Analytics Kit. Project setting > Manage API > Enable analytics kit toggle button.
Step 5: Generating a Signing Certificate Fingerprint.
Step 6: Configuring the Signing Certificate Fingerprint.
Step 7: Download your agconnect-services.json file, paste it into the app root directory.
Client application development process
Follow the steps.
Step 1: Create Android application in the Android studio (Any IDE which is your favorite)
Step 2: Add the App level gradle dependencies. Choose inside project Android > app > build.gradle.
Code:
apply plugin: 'com.android.application'
apply plugin: 'com.huawei.agconnect'
dependencies {
implementation 'com.huawei.hms:safetydetect:5.2.0.300'
}
Root level gradle dependencies.
Code:
maven { url 'https://developer.huawei.com/repo/' }
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
Step 3: Build Application.
SysIntegrity: Checks whether the device is secure( e.g. whether it is rooted)
Java:
import android.app.AlertDialog;
import android.content.Context;
import android.util.Base64;
import android.util.Log;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;
import com.huawei.hms.common.ApiException;
import com.huawei.hms.support.api.entity.safetydetect.SysIntegrityResp;
import com.huawei.hms.support.api.safetydetect.SafetyDetect;
import com.huawei.hms.support.api.safetydetect.SafetyDetectClient;
import com.huawei.hms.support.api.safetydetect.SafetyDetectStatusCodes;
import org.json.JSONException;
import org.json.JSONObject;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class SysIntegrity {
private static final String TAG= "Safety SysIntegrity";
Context context;
String APP_ID;
private SafetyDetectClient client;
public SysIntegrity(SafetyDetectClient client,Context context, String APP_ID) {
this.context = context;
this.APP_ID = APP_ID;
this.client=client;
}
public void invoke() {
// TODO(developer): Change the nonce generation to include your own, used once value,
// ideally from your remote server.
byte[] nonce = new byte[24];
try {
SecureRandom random;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
random = SecureRandom.getInstanceStrong();
} else {
random = SecureRandom.getInstance("SHA1PRNG");
}
random.nextBytes(nonce);
} catch (
NoSuchAlgorithmException e) {
Log.e(TAG, e.getMessage());
}
// TODO(developer): Change your app ID. You can obtain your app ID in AppGallery Connect.
Task task = client.sysIntegrity(nonce, APP_ID);
task.addOnSuccessListener(new OnSuccessListener<SysIntegrityResp>() {
@Override
public void onSuccess(SysIntegrityResp response) {
// Indicates communication with the service was successful.
// Use response.getResult() to get the result data.
String jwsStr = response.getResult();
String[] jwsSplit = jwsStr.split("\\.");
String jwsPayloadStr = jwsSplit[1];
String payloadDetail = new String(Base64.decode(jwsPayloadStr.getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE), StandardCharsets.UTF_8);
try {
final JSONObject jsonObject = new JSONObject(payloadDetail);
final boolean basicIntegrity = jsonObject.getBoolean("basicIntegrity");
String isBasicIntegrity = String.valueOf(basicIntegrity);
String basicIntegrityResult = "Basic Integrity: " + isBasicIntegrity;
showAlert(basicIntegrityResult);
Log.i(TAG, basicIntegrityResult);
if (!basicIntegrity) {
String advice = "Advice: " + jsonObject.getString("advice");
Log.i("Advice log", advice);
}
} catch (JSONException e) {
String errorMsg = e.getMessage();
showAlert(errorMsg + "unknown error");
Log.e(TAG, errorMsg != null ? errorMsg : "unknown error");
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
// An error occurred while communicating with the service.
if (e instanceof ApiException) {
// An error with the HMS API contains some
// additional details.
ApiException apiException = (ApiException) e;
// You can retrieve the status code using
// the apiException.getStatusCode() method.
showAlert("Error: " + SafetyDetectStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " + apiException.getMessage());
Log.e(TAG, "Error: " + SafetyDetectStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " + apiException.getMessage());
} else {
// A different, unknown type of error occurred.
showAlert("ERROR:"+e.getMessage());
Log.e(TAG, "ERROR:" + e.getMessage());
}
}
});
}
public void showAlert(String message){
AlertDialog alertDialog = new AlertDialog.Builder(context).create();
alertDialog.setTitle("SysIntegrity");
alertDialog.setMessage(message);
alertDialog.show();
}
}
AppsCheck: Determinate malicious apps and provides you with a list of malicious apps.
Java:
import android.app.AlertDialog;
import android.content.Context;
import android.util.Log;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;
import com.huawei.hms.common.ApiException;
import com.huawei.hms.support.api.entity.core.CommonCode;
import com.huawei.hms.support.api.entity.safetydetect.MaliciousAppsData;
import com.huawei.hms.support.api.entity.safetydetect.MaliciousAppsListResp;
import com.huawei.hms.support.api.safetydetect.SafetyDetect;
import com.huawei.hms.support.api.safetydetect.SafetyDetectClient;
import com.huawei.hms.support.api.safetydetect.SafetyDetectStatusCodes;
import java.util.List;
public class AppsCheck {
private static final String TAG= "Safety AppsCheck";
Context context;
String APP_ID;
private SafetyDetectClient client;
public AppsCheck(SafetyDetectClient client,Context context, String APP_ID) {
this.context = context;
this.APP_ID = APP_ID;
this.client=client;
}
public void invokeGetMaliciousApps() {
Task task = client.getMaliciousAppsList();
task.addOnSuccessListener(new OnSuccessListener<MaliciousAppsListResp>() {
@Override
public void onSuccess(MaliciousAppsListResp maliciousAppsListResp) {
// Indicates communication with the service was successful.
// Use resp.getMaliciousApps() to get malicious apps data.
List<MaliciousAppsData> appsDataList = maliciousAppsListResp.getMaliciousAppsList();
// Indicates get malicious apps was successful.
if (maliciousAppsListResp.getRtnCode() == CommonCode.OK) {
if (appsDataList.isEmpty()) {
// Indicates there are no known malicious apps.
showAlert("There are no known potentially malicious apps installed.");
Log.i(TAG, "There are no known potentially malicious apps installed.");
} else {
showAlert("Potentially malicious apps are installed!");
Log.i(TAG, "Potentially malicious apps are installed!");
for (MaliciousAppsData maliciousApp : appsDataList) {
Log.i(TAG, "Information about a malicious app:");
// Use getApkPackageName() to get APK name of malicious app.
Log.i(TAG, "APK: " + maliciousApp.getApkPackageName());
// Use getApkSha256() to get APK sha256 of malicious app.
Log.i(TAG, "SHA-256: " + maliciousApp.getApkSha256());
// Use getApkCategory() to get category of malicious app.
// Categories are defined in AppsCheckConstants
Log.i(TAG, "Category: " + maliciousApp.getApkCategory());
}
}
} else {
showAlert("getMaliciousAppsList fialed: " + maliciousAppsListResp.getErrorReason());
Log.e(TAG, "getMaliciousAppsList fialed: " + maliciousAppsListResp.getErrorReason());
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
// An error occurred while communicating with the service.
if (e instanceof ApiException) {
// An error with the HMS API contains some
// additional details.
ApiException apiException = (ApiException) e;
// You can retrieve the status code using the apiException.getStatusCode() method.
showAlert("Error: " + SafetyDetectStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " + apiException.getStatusMessage());
Log.e(TAG, "Error: " + SafetyDetectStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " + apiException.getStatusMessage());
} else {
// A different, unknown type of error occurred.
Log.e(TAG, "ERROR: " + e.getMessage());
}
}
});
}
public void showAlert(String message){
AlertDialog alertDialog = new AlertDialog.Builder(context).create();
alertDialog.setTitle("AppsCheck");
alertDialog.setMessage(message);
alertDialog.show();
}
}
URLCheck: Provide malicious URL detection capabilities.
Java:
import android.app.AlertDialog;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hms.common.ApiException;
import com.huawei.hms.support.api.entity.safetydetect.UrlCheckResponse;
import com.huawei.hms.support.api.entity.safetydetect.UrlCheckThreat;
import com.huawei.hms.support.api.safetydetect.SafetyDetect;
import com.huawei.hms.support.api.safetydetect.SafetyDetectClient;
import com.huawei.hms.support.api.safetydetect.SafetyDetectStatusCodes;
import java.util.List;
public class URLCheck {
private static final String TAG= "Safety URLCheck";
Context context;
String APP_ID;
private SafetyDetectClient client;
public URLCheck(SafetyDetectClient client, Context context, String APP_ID) {
this.client = client;
this.context = context;
this.APP_ID = APP_ID;
}
public void callUrlCheckApi() {
client.urlCheck("https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/SafetyDetectWiFiDetectAPIDevelopment", APP_ID,
// Specify url threat type
UrlCheckThreat.MALWARE,
UrlCheckThreat.PHISHING)
.addOnSuccessListener(new OnSuccessListener<UrlCheckResponse>() {
/**
* Called after successfully communicating with the SafetyDetect API.
* The #onSuccess callback receives an
* {@link com.huawei.hms.support.api.entity.safetydetect.UrlCheckResponse} that contains a
* list of UrlCheckThreat that contains the threat type of the Url.
*/
@Override
public void onSuccess(UrlCheckResponse urlCheckResponse) {
// Indicates communication with the service was successful.
// Identify any detected threats.
// Call getUrlCheckResponse method of UrlCheckResponse then you can get List<UrlCheckThreat> .
// If List<UrlCheckThreat> is empty , that means no threats found , else that means threats found.
List<UrlCheckThreat> list = urlCheckResponse.getUrlCheckResponse();
if (list.isEmpty()) {
// No threats found.
showAlert("No Threats found!");
Log.i(TAG,"No Threats found!");
} else {
// Threats found!
showAlert("Threats found!");
Log.i(TAG,"Threats found!");
}
}
})
.addOnFailureListener(new OnFailureListener() {
/**
* Called when an error occurred when communicating with the SafetyDetect API.
*/
@Override
public void onFailure(Exception e) {
// An error with the Huawei Mobile Service API contains some additional details.
String errorMsg;
if (e instanceof ApiException) {
ApiException apiException = (ApiException) e;
errorMsg = "Error: " +
SafetyDetectStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " +
e.getMessage();
// You can use the apiException.getStatusCode() method to get the status code.
// Note: If the status code is SafetyDetectStatusCodes.CHECK_WITHOUT_INIT, you need to call initUrlCheck().
} else {
// Unknown type of error has occurred.
errorMsg = e.getMessage();
}
showAlert(errorMsg);
Log.d(TAG, errorMsg);
Toast.makeText(context.getApplicationContext(), errorMsg, Toast.LENGTH_SHORT).show();
}
});
}
public void showAlert(String message){
AlertDialog alertDialog = new AlertDialog.Builder(context).create();
alertDialog.setTitle("URLCheck");
alertDialog.setMessage(message);
alertDialog.show();
}
}
UserDetect: Determinate fake users and bots.
Java:
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hms.common.ApiException;
import com.huawei.hms.support.api.entity.safetydetect.UserDetectResponse;
import com.huawei.hms.support.api.safetydetect.SafetyDetect;
import com.huawei.hms.support.api.safetydetect.SafetyDetectStatusCodes;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
public class UserDetect {
private static final String TAG= "Safety User Detect";
Context context;
String APP_ID;
public UserDetect(Context context, String APP_ID) {
this.APP_ID=APP_ID;
this.context=context;
}
public void detect() {
Log.i(TAG, "User detection start.");
SafetyDetect.getClient(context)
.userDetection(APP_ID)
.addOnSuccessListener(new OnSuccessListener<UserDetectResponse>() {
/**
* Called after successfully communicating with the SafetyDetect API.
* The #onSuccess callback receives an
* {@link UserDetectResponse} that contains a
* responseToken that can be used to get user detect result.
*/
@Override
public void onSuccess(UserDetectResponse userDetectResponse) {
// Indicates communication with the service was successful.
Log.i(TAG, "User detection succeed, response = " + userDetectResponse);
boolean verifySucceed = verify(userDetectResponse.getResponseToken());
if (verifySucceed) {
Toast.makeText(context.getApplicationContext(),
"User detection succeed and verify succeed",
Toast.LENGTH_SHORT)
.show();
} else {
Toast.makeText(context.getApplicationContext(),
"User detection succeed but verify fail,"
+ "please replace verify url with your's server address",
Toast.LENGTH_SHORT)
.show();
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
// There was an error communicating with the service.
String errorMsg;
if (e instanceof ApiException) {
// An error with the HMS API contains some additional details.
ApiException apiException = (ApiException) e;
errorMsg = SafetyDetectStatusCodes.getStatusCodeString(apiException.getStatusCode())
+ ": " + apiException.getMessage();
// You can use the apiException.getStatusCode() method to get the status code.
} else {
// Unknown type of error has occurred.
errorMsg = e.getMessage();
}
Log.i(TAG, "User detection fail. Error info: " + errorMsg);
Toast.makeText(context.getApplicationContext(), errorMsg, Toast.LENGTH_SHORT).show();
}
});
}
/**
* Send responseToken to your server to get the result of user detect.
*/
private static boolean verify(final String responseToken) {
try {
return new AsyncTask<String, Void, Boolean>() {
@Override
protected Boolean doInBackground(String... strings) {
String input = strings[0];
JSONObject jsonObject = new JSONObject();
try {
// TODO(developer): Replace the baseUrl with your own server address,better not hard code.
String baseUrl = "https://www.example.com/userdetect/verify";
jsonObject.put("response", input);
String result = sendPost(baseUrl, jsonObject);
JSONObject resultJson = new JSONObject(result);
boolean success = resultJson.getBoolean("success");
// if success is true that means the user is real human instead of a robot.
Log.i(TAG, "verify: result = " + success);
return success;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}.execute(responseToken).get();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
return false;
}
}
/**
* post the response token to yur own server.
*/
private static String sendPost(String baseUrl, JSONObject postDataParams) throws Exception {
URL url = new URL(baseUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(20000);
conn.setConnectTimeout(20000);
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
try (OutputStream os = conn.getOutputStream(); BufferedWriter writer =
new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
writer.write(postDataParams.toString());
writer.flush();
}
int responseCode = conn.getResponseCode(); // To Check for 200
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = in.readLine()) != null) {
sb.append(line);
break;
}
in.close();
return sb.toString();
}
return null;
}
}
WifiDetect: Check Wifi to be connected is secure.
1: Failed to obtain the Wi-Fi status.
0: No Wi-Fi is connected.
1: The connected Wi-Fi is secure.
2: The connected Wi-Fi is insecure.
Java:
import android.app.AlertDialog;
import android.content.Context;
import android.util.Log;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;
import com.huawei.hms.common.ApiException;
import com.huawei.hms.support.api.entity.safetydetect.WifiDetectResponse;
import com.huawei.hms.support.api.safetydetect.SafetyDetectClient;
import com.huawei.hms.support.api.safetydetect.SafetyDetectStatusCodes;
public class WifiCheck {
private SafetyDetectClient client;
private static final String TAG= "Safety WIFICheck";
Context context;
String APP_ID;
public WifiCheck(SafetyDetectClient client, Context context, String APP_ID) {
this.client = client;
this.context = context;
this.APP_ID = APP_ID;
}
public void invokeGetWifiDetectStatus() {
Log.i(TAG, "Start to getWifiDetectStatus!");
Task task = client.getWifiDetectStatus();
task.addOnSuccessListener(new OnSuccessListener<WifiDetectResponse>() {
@Override
public void onSuccess(WifiDetectResponse wifiDetectResponse) {
int wifiDetectStatus = wifiDetectResponse.getWifiDetectStatus();
showAlert("\n-1: Failed to obtain the Wi-Fi status. \n" + "0: No Wi-Fi is connected. \n" + "1: The connected Wi-Fi is secure. \n" + "2: The connected Wi-Fi is insecure." + "wifiDetectStatus is: " + wifiDetectStatus);
Log.i(TAG, "\n-1: Failed to obtain the Wi-Fi status. \n" + "0: No Wi-Fi is connected. \n" + "1: The connected Wi-Fi is secure. \n" + "2: The connected Wi-Fi is insecure.");
Log.i(TAG, "wifiDetectStatus is: " + wifiDetectStatus);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
if (e instanceof ApiException) {
ApiException apiException = (ApiException) e;
Log.e(TAG,
"Error: " + apiException.getStatusCode() + ":"
+ SafetyDetectStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": "
+ apiException.getStatusMessage());
showAlert("Error: " + apiException.getStatusCode() + ":"
+ SafetyDetectStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": "
+ apiException.getStatusMessage());
} else {
Log.e(TAG, "ERROR! " + e.getMessage());
showAlert("ERROR! " + e.getMessage());
}
}
});
}
public void showAlert(String message){
AlertDialog alertDialog = new AlertDialog.Builder(context).create();
alertDialog.setTitle("WifiCheck");
alertDialog.setMessage(message);
alertDialog.show();
}
}
Now call the required things
Java:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.buttonSysIntegrity:
sysIntegrity= new SysIntegrity(client,MainActivity.this,APP_ID);
sysIntegrity.invoke();
break;
case R.id.buttonAppsCheck:
appsCheck=new AppsCheck(client,MainActivity.this,APP_ID);
appsCheck.invokeGetMaliciousApps();
break;
case R.id.buttonURLCheck:
urlCheck=new URLCheck(client,MainActivity.this,APP_ID);
urlCheck.callUrlCheckApi();
break;
case R.id.buttonUserDetect:
userDetect=new UserDetect(MainActivity.this,APP_ID);
userDetect.detect();
break;
case R.id.buttonWifiCheck:
wifiCheck=new WifiCheck(client,MainActivity.this,APP_ID);
wifiCheck.invokeGetWifiDetectStatus();
break;
default:
break;
}
}
Result​
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Tips and Tricks​
Download latest HMS Flutter plugin.
Check dependencies downloaded properly.
Latest HMS Core APK is required.
Set minSDK 19 or later.
WifiDetect function available only in Chinese mainland.
UserDetect function not available in Chinese mainland.
Conclusion​
In this article, we have learnt integration of Huawei safety detect and types of security provided by Huawei safety detect kit. Nowadays everything is happening over the phone, so as developer it is our responsibility to provide security to application. Otherwise users do not use the application. Thank Huawei for giving such great Safety detect kit to build secure application.
Reference​
Safety detect

Thanks for sharing!!!

Can this app works on offline?

Related

Android App Bundle - Features on demand--part 1

More articles like this, you can visit HUAWEI Developer Forum.​
Introduction:
Dynamic Ability is a service in which HUAWEI AppGallery implements dynamic loading based on the Android App Bundle technology.
Apps integrated with the Dynamic Ability SDK can dynamically download features or language packages from HUAWEI AppGallery as required, reducing the unnecessary consumption of network traffic and device storage space.
What is Android App Bundle?
It is a new upload format that includes all your app’s compiled code and resources, but defers APK generation and signing to AppGallery. Traditionally, Android apps are distributed using a special file called an Android Package (.apk).
Advantage of using App Bundle format:
· Dynamic Ability Feature: AppGallery’ s new app serving model, called Dynamic Ability, uses your app bundle to generate and serve optimized APKs for each user’s device configuration, so they download only the code and resources they need to run your app. For example, you don’t need other languages strings if you have set English as your default language.
· No need to manually manage multiple APKs: You no longer have to build, sign, and manage multiple APKs to support different devices, and users get smaller, more optimized downloads. For example, now you don’t have to create multiple APKs for devices with different screen resolutions.
· Dynamic Feature Module: These modules contain features and assets that you can decide not to include when users first download and install your app. using the Dynamic Ability SDK, your app can later request to download those modules as dynamic feature APKs. For example, video calling feature and camera filters can be downloaded later on demand.
· Reduced APK size: Using Split APK mechanism, AppGallery can break up a large app into smaller, discrete packages that are installed on a user’s device as required. On average, apps published with app bundles are 20% smaller in size.
Let’s start Development process:
We need to follow the below step in order to achieve Dynamic Ability.
1. Create an App on AppGallery.
2. Create an Android Studio Project.
3. Integrate Dynamic Ability SDK.
4. Launch the app.
Create an App on AppGallery:
We need to create an app:
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
App package manually:
Note: Download json file and add into your project.
Create an Android Studio Project:
Select project templet:
Create app name, package name, and project location:
Now we need to add Dynamic Ability module in our project:
Create module name and package name:
Create module download options:
Sync project and add the following plugin in module’s Gradle file:
apply plugin: 'com.android.dynamic-feature'
Let’s see module’s manifest.xml file:
Code:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.huawei.android.dynamicfeaturesplit.splitsamplefeature01">
<dist:module
dist:onDemand="true"
dist:title="@string/title_splitsamplefeature01">
<dist:fusing dist:include="true" />
</dist:module>
<application>
<activity android:name=".FeatureActivity"></activity>
</application>
</manifest>
<dist:module>: This new XML element defines attributes that determine how the module is packaged and distributed as APKs.
distnDemand="true|false": Specifies whether the module should be available as an on demand download.
dist:title="@string/feature_name": Specifies a user-facing title for the module.
<dist:fusing include="true|false" />: Specifies whether to include the module in multi-APKs that target devices running Android 4.4 (API level 20) and lower.
Integrate Dynamic Ability SDK:
1. Open the build.gradle file (usually in the root directory) of your project. Go to allprojects > repositories and configure the Maven repository address for the SDK.
Code:
allprojects{
repositories{
maven {url 'http://developer.huawei.com/repo/'}
...
}
}
2. Add the following code to the build.gradle file (usually app/build.gradle) in the app directory to integrate the Dynamic Ability SDK:
Code:
dependencies {
implementation 'com.huawei.hms:dynamicability:1.0.11.302'
...
}
· Let’s sync the project and start app implementation:
We have created following project structure.
·Let’s see the implementation of Application class:
Code:
import android.app.Application;
import android.content.Context;
import android.util.Log;
import com.huawei.hms.feature.dynamicinstall.FeatureCompat;
public class DynamicFeatureSampleApplication extends Application {
public static final String TAG = DynamicFeatureSampleApplication.class.getSimpleName();
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
FeatureCompat.install(base);
} catch (Exception e) {
Log.w(TAG, "", e);
}
}
}
Configure your app in your Android project, override the attachBaseContext() method in the project, and call FeatureCompat.install to initialize the Dynamic Ability SDK.
· Let’s the implementation of Activity:
Code:
package com.huawei.android.dynamicfeaturesplit;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.huawei.hms.feature.install.FeatureInstallManager;
import com.huawei.hms.feature.install.FeatureInstallManagerFactory;
import com.huawei.hms.feature.listener.InstallStateListener;
import com.huawei.hms.feature.model.FeatureInstallException;
import com.huawei.hms.feature.model.FeatureInstallRequest;
import com.huawei.hms.feature.model.FeatureInstallSessionStatus;
import com.huawei.hms.feature.model.InstallState;
import com.huawei.hms.feature.tasks.FeatureTask;
import com.huawei.hms.feature.tasks.listener.OnFeatureCompleteListener;
import com.huawei.hms.feature.tasks.listener.OnFeatureFailureListener;
import com.huawei.hms.feature.tasks.listener.OnFeatureSuccessListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class SampleEntry extends Activity {
private static final String TAG = SampleEntry.class.getSimpleName();
private ProgressBar progressBar;
private FeatureInstallManager mFeatureInstallManager;
private int sessionId = 10086;
private InstallStateListener mStateUpdateListener = new InstallStateListener() {
@Override
public void onStateUpdate(InstallState state) {
Log.d(TAG, "install session state " + state);
if (state.status() == FeatureInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
try {
mFeatureInstallManager.triggerUserConfirm(state, SampleEntry.this, 1);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
return;
}
if (state.status() == FeatureInstallSessionStatus.REQUIRES_PERSON_AGREEMENT) {
try {
mFeatureInstallManager.triggerUserConfirm(state, SampleEntry.this, 1);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
return;
}
if (state.status() == FeatureInstallSessionStatus.INSTALLED) {
Log.i(TAG, "installed success ,can use new feature");
makeToast("installed success , can test new feature ");
return;
}
if (state.status() == FeatureInstallSessionStatus.UNKNOWN) {
Log.e(TAG, "installed in unknown status");
makeToast("installed in unknown status ");
return;
}
if (state.status() == FeatureInstallSessionStatus.DOWNLOADING) {
long process = state.bytesDownloaded() * 100 / state.totalBytesToDownload();
Log.d(TAG, "downloading percentage: " + process);
makeToast("downloading percentage: " + process);
return;
}
if (state.status() == FeatureInstallSessionStatus.FAILED) {
Log.e(TAG, "installed failed, errorcode : " + state.errorCode());
makeToast("installed failed, errorcode : " + state.errorCode());
return;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progress_bar);
mFeatureInstallManager = FeatureInstallManagerFactory.create(this);
}
@Override
protected void onResume() {
super.onResume();
if (mFeatureInstallManager != null) {
mFeatureInstallManager.registerInstallListener(mStateUpdateListener);
}
}
@Override
protected void onPause() {
super.onPause();
if (mFeatureInstallManager != null) {
mFeatureInstallManager.unregisterInstallListener(mStateUpdateListener);
}
}
/**
* install feature
*
* @param view the view
*/
public void installFeature(View view) {
if (mFeatureInstallManager == null) {
return;
}
// start install
FeatureInstallRequest request = FeatureInstallRequest.newBuilder()
.addModule("SplitSampleFeature01")
.build();
final FeatureTask<Integer> task = mFeatureInstallManager.installFeature(request);
task.addOnListener(new OnFeatureSuccessListener<Integer>() {
@Override
public void onSuccess(Integer integer) {
Log.d(TAG, "load feature onSuccess.session id:" + integer);
}
});
task.addOnListener(new OnFeatureFailureListener<Integer>() {
@Override
public void onFailure(Exception exception) {
if (exception instanceof FeatureInstallException) {
int errorCode = ((FeatureInstallException) exception).getErrorCode();
Log.d(TAG, "load feature onFailure.errorCode:" + errorCode);
} else {
exception.printStackTrace();
}
}
});
task.addOnListener(new OnFeatureCompleteListener<Integer>() {
@Override
public void onComplete(FeatureTask<Integer> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to start install.");
if (featureTask.isSuccessful()) {
Integer result = featureTask.getResult();
sessionId = result;
Log.d(TAG, "succeed to start install. session id :" + result);
} else {
Log.d(TAG, "fail to start install.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});
Log.d(TAG, "start install func end");
}
/**
* start feature
*
* @param view the view
*/
public void startFeature01(View view) {
// test getInstallModules
Set<String> moduleNames = mFeatureInstallManager.getAllInstalledModules();
Log.d(TAG, "getInstallModules : " + moduleNames);
if (moduleNames != null && moduleNames.contains("SplitSampleFeature01")) {
try {
startActivity(new Intent(this, Class.forName(
"com.huawei.android.dynamicfeaturesplit.splitsamplefeature01.FeatureActivity")));
} catch (Exception e) {
Log.w(TAG, "", e);
}
}
}
/**
* cancel install task
*
* @param view the view
*/
public void abortInstallFeature(View view) {
Log.d(TAG, "begin abort_install : " + sessionId);
FeatureTask<Void> task = mFeatureInstallManager.abortInstallFeature(sessionId);
task.addOnListener(new OnFeatureCompleteListener<Void>() {
@Override
public void onComplete(FeatureTask<Void> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to abort_install.");
if (featureTask.isSuccessful()) {
Log.d(TAG, "succeed to abort_install.");
} else {
Log.d(TAG, "fail to abort_install.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});
}
/**
* get install task state
*
* @param view the view
*/
public void getInstallState(View view) {
Log.d(TAG, "begin to get session state for: " + sessionId);
FeatureTask<InstallState> task = mFeatureInstallManager.getInstallState(sessionId);
task.addOnListener(new OnFeatureCompleteListener<InstallState>() {
@Override
public void onComplete(FeatureTask<InstallState> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to get session state.");
if (featureTask.isSuccessful()) {
InstallState state = featureTask.getResult();
Log.d(TAG, "succeed to get session state.");
Log.d(TAG, state.toString());
} else {
Log.e(TAG, "failed to get session state.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});
}
/**
* get states of all install tasks
*
* @param view the view
*/
public void getAllInstallStates(View view) {
Log.d(TAG, "begin to get all session states.");
FeatureTask<List<InstallState>> task = mFeatureInstallManager.getAllInstallStates();
task.addOnListener(new OnFeatureCompleteListener<List<InstallState>>() {
@Override
public void onComplete(FeatureTask<List<InstallState>> featureTask) {
Log.d(TAG, "complete to get session states.");
if (featureTask.isSuccessful()) {
Log.d(TAG, "succeed to get session states.");
List<InstallState> stateList = featureTask.getResult();
for (InstallState state : stateList) {
Log.d(TAG, state.toString());
}
} else {
Log.e(TAG, "fail to get session states.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
});
}
/**
* deffer to install features
*
* @param view the view
*/
public void delayedInstallFeature(View view) {
List<String> features = new ArrayList<>();
features.add("SplitSampleFeature01");
FeatureTask<Void> task = mFeatureInstallManager.delayedInstallFeature(features);
task.addOnListener(new OnFeatureCompleteListener<Void>() {
@Override
public void onComplete(FeatureTask<Void> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to delayed_Install");
if (featureTask.isSuccessful()) {
Log.d(TAG, "succeed to delayed_install");
} else {
Log.d(TAG, "fail to delayed_install.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});
}
/**
* uninstall features
*
* @param view the view
*/
public void delayedUninstallFeature(View view) {
List<String> features = new ArrayList<>();
features.add("SplitSampleFeature01");
FeatureTask<Void> task = mFeatureInstallManager.delayedUninstallFeature(features);
task.addOnListener(new OnFeatureCompleteListener<Void>() {
@Override
public void onComplete(FeatureTask<Void> featureTask) {
if (featureTask.isComplete()) {
Log.d(TAG, "complete to delayed_uninstall");
if (featureTask.isSuccessful()) {
Log.d(TAG, "succeed to delayed_uninstall");
} else {
Log.d(TAG, "fail to delayed_uninstall.");
Exception exception = featureTask.getException();
exception.printStackTrace();
}
}
}
});
}
/**
* install languages
*
* @param view the view
*/
public void loadLanguage(View view) {
if (mFeatureInstallManager == null) {
return;
}
// start install
Set<String> languages = new HashSet<>();
languages.add("fr-FR");
FeatureInstallRequest.Builder builder = FeatureInstallRequest.newBuilder();
for (String lang : languages) {
builder.addLanguage(Locale.forLanguageTag(lang));
}
FeatureInstallRequest request = builder.build();
FeatureTask<Integer> task = mFeatureInstallManager.installFeature(request);
task.addOnListener(new OnFeatureSuccessListener<Integer>() {
@Override
public void onSuccess(Integer result) {
Log.d(TAG, "onSuccess callback result " + result);
}
});
task.addOnListener(new OnFeatureFailureListener<Integer>() {
@Override
public void onFailure(Exception exception) {
if (exception instanceof FeatureInstallException) {
Log.d(TAG, "onFailure callback "
+ ((FeatureInstallException) exception).getErrorCode());
} else {
Log.d(TAG, "onFailure callback ", exception);
}
}
});
task.addOnListener(new OnFeatureCompleteListener<Integer>() {
@Override
public void onComplete(FeatureTask<Integer> task) {
Log.d(TAG, "onComplete callback");
}
});
}
private void makeToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
}
· We have integrated sdk based classes and callbacks to achieve Dynamic Ability feature in our activity based class.
· Let’s implement module based activity class:
Code:
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
import android.widget.Toast;
import com.huawei.hms.feature.dynamicinstall.FeatureCompat;
public class FeatureActivity extends Activity {
private static final String TAG = FeatureActivity.class.getSimpleName();
static {
System.loadLibrary("feature-native-lib");
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
try {
FeatureCompat.install(newBase);
} catch (Exception e) {
Log.w(TAG, "", e);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_feature);
ImageView mImageView = findViewById(R.id.iv_load_png);
mImageView.setImageDrawable(getResources().getDrawable(R.mipmap.google));
Toast.makeText(this, "from feature " + stringFromJNI(), Toast.LENGTH_LONG).show();
}
/**
* String from jni string.
*
* @return the string
*/
public native String stringFromJNI();
}
· In an activity of a dynamic feature module, call FeatureCompat.install to initialize the Dynamic Ability SDK.
Launch the app:
· Let’s see the result-
If you have any doubts or queries. Please leave your valuable comment in the comment section

Expert: Xamarin Android College Campus Placement App Using In-App Purchase and Login with Huawei ID

Overview
In this article, I will create a College Campus Placement Centre Demo App which highlights ongoing college placement with all listed companies and their details. Student can easily apply and redeem points through IAP. I have integrated HMS Account and IAP Kit which is based on Cross-platform Technology Xamarin.
HMS IAP Service Introduction
HMS In-App Purchase Kit allows purchasing any product from the application with highly secure payment. Users can purchase a variety of products or services, including common virtual products and subscriptions, directly within your app. It also provides a product management system (PMS) for managing the prices and languages of in-app products (including games) in multiple locations.
These are the following 3 types of in-app products supported by the IAP:
1. Consumable: Consumables are used once, are depleted, and can be purchased again.
2. Non-consumable: Non-consumables are purchased once and do not expire.
3. Auto-renewable subscriptions: Users can purchase access to value-added functions or content in a specified period of time. The subscriptions are automatically renewed on a recurring basis until users decide to cancel.
Account Kit Service Introduction
HMS Account Kit allows you to connect to the Huawei ecosystem using your HUAWEI ID from a range of devices, such as mobile phones, tablets, and smart screens.
It’s a simple, secure, and quick sign-in and authorization functions. Instead of entering accounts and passwords and waiting for authentication.
Complies with international standards and protocols such as OAuth2.0 and OpenID Connect, and supports two-factor authentication (password authentication and mobile number authentication) to ensure high security.
Prerequisite
1. Xamarin Framework
2. Huawei phone
3. Visual Studio 2019
App Gallery Integration process
1. Sign In and Create or Choose a project on AppGallery Connect portal.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
2. Navigate to Project settings > download the configuration file.
3. Navigate to General Information > Data Storage location.
4. Navigate to Manage APIs > enable APIs to require by an application.
5. Navigate to My apps > Operate, and then enter details in Add Product.
6. Click View and Edit in the above screenshot, enter Product price details, and then click Save.
7. Click Activate for product activation.
Xamarin Account Kit Setup Process
1. Download Xamarin Plugin all the aar and zip files from below URL:
Document
developer.huawei.com
2. Open the XHwid-5.03.302.sln solution in Visual Studio.
Xamarin IAP Kit Setup Process
1. Download Xamarin Plugin all the aar and zip files from below URL:
Document
developer.huawei.com
2. Open the XIAP-5.0.2.300.sln solution in Visual Studio.
3. Navigate to Solution Explorer and Right-click on jar Add > Existing Item and choose aar file which downloads in Step 1.
4. Right-click on added aar file then choose Properties > Build Action > LibraryProjectZip.
Note: Repeat Step 3 & 4 for all aar file.
5. Build the Library and make dll files.
Xamarin App Development
1. Open Visual Studio 2019 and Create a new project.
2. Navigate to Solution Explore > Project > Assets > Add JSON file.
3. Navigate to Solution Explore > Project > Add > Add New Folder.
4. Navigate to Folder(created) > Add > Add Existing and add all DLL files.
5. Right click > Properties > Build Action > None.
6. Navigate to Solution Explore > Project > Reference > Right Click > Add References then Navigate to Browse and add all DLL files from the recently added Folder.
7. Added reference then click Ok.
MainActivity.cs
This activity performs all the operation regarding login with Huawei Id.
Java:
using System;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using Android.Support.Design.Widget;
using Android.Support.V4.App;
using Android.Support.V4.Content;
using Android.Support.V7.App;
using Android.Util;
using Android.Views;
using Android.Widget;
using Com.Huawei.Agconnect.Config;
using Com.Huawei.Hmf.Tasks;
using Com.Huawei.Hms.Common;
using Com.Huawei.Hms.Iap;
using Com.Huawei.Hms.Iap.Entity;
using Com.Huawei.Hms.Support.Hwid;
using Com.Huawei.Hms.Support.Hwid.Request;
using Com.Huawei.Hms.Support.Hwid.Result;
using Com.Huawei.Hms.Support.Hwid.Service;
namespace PlacementApp
{
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
private Button btnLoginWithHuaweiId;
private HuaweiIdAuthParams mAuthParam;
public static IHuaweiIdAuthService mAuthManager;
private static String TAG = "MainActivity";
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
SetContentView(Resource.Layout.activity_main);
Android.Support.V7.Widget.Toolbar toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
btnLoginWithHuaweiId = FindViewById<Button>(Resource.Id.btn_huawei_id);
// Write code for Huawei id button click
mAuthParam = new HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DefaultAuthRequestParam)
.SetIdToken().SetEmail()
.SetAccessToken()
.CreateParams();
mAuthManager = HuaweiIdAuthManager.GetService(this, mAuthParam);
// Click listener for each button
btnLoginWithHuaweiId.Click += delegate
{
StartActivityForResult(mAuthManager.SignInIntent, 1011);
};
CheckIfIAPAvailable();
/*FloatingActionButton fab = FindViewById<FloatingActionButton>(Resource.Id.fab);
fab.Click += FabOnClick;*/
//check permissions
checkPermission(new string[] { Android.Manifest.Permission.Internet,
Android.Manifest.Permission.AccessNetworkState,
Android.Manifest.Permission.ReadSms,
Android.Manifest.Permission.ReceiveSms,
Android.Manifest.Permission.SendSms,
Android.Manifest.Permission.BroadcastSms}, 100);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == 1011)
{
//login success
Task authHuaweiIdTask = HuaweiIdAuthManager.ParseAuthResultFromIntent(data);
if (authHuaweiIdTask.IsSuccessful)
{
AuthHuaweiId huaweiAccount = (AuthHuaweiId)authHuaweiIdTask.TaskResult();
Log.Info(TAG, "signIn get code success.");
Log.Info(TAG, "ServerAuthCode: " + huaweiAccount.AuthorizationCode);
Toast.MakeText(Android.App.Application.Context, "SignIn Success", ToastLength.Short).Show();
ManageHomeScreen(huaweiAccount, true);
}
else
{
Log.Info(TAG, "signIn failed: " + ((ApiException)authHuaweiIdTask.Exception).StatusCode);
Toast.MakeText(Android.App.Application.Context, ((ApiException)authHuaweiIdTask.Exception).StatusCode.ToString(), ToastLength.Short).Show();
Toast.MakeText(Android.App.Application.Context, "SignIn Failed", ToastLength.Short).Show();
ManageHomeScreen(null, false);
}
}
}
public void ManageHomeScreen(AuthHuaweiId data, Boolean loginStatus)
{
if (loginStatus)
{
btnLoginWithHuaweiId.Visibility = ViewStates.Gone;
}
else
{
btnLoginWithHuaweiId.Visibility = ViewStates.Visible;
}
}
public void checkPermission(string[] permissions, int requestCode)
{
foreach (string permission in permissions)
{
if (ContextCompat.CheckSelfPermission(this, permission) == Permission.Denied)
{
ActivityCompat.RequestPermissions(this, permissions, requestCode);
}
}
}
/*private void FabOnClick(object sender, EventArgs eventArgs)
{
View view = (View) sender;
Snackbar.Make(view, "Replace with your own action", Snackbar.LengthLong)
.SetAction("Action", (Android.Views.View.IOnClickListener)null).Show();
}*/
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
protected override void AttachBaseContext(Context context)
{
base.AttachBaseContext(context);
AGConnectServicesConfig config = AGConnectServicesConfig.FromContext(context);
config.OverlayWith(new HmsLazyInputStream(context));
}
private void CancelAuthorisation()
{
Task cancelAuthorizationTask = mAuthManager.CancelAuthorization();
Log.Info(TAG, "Cancel Authorisation");
cancelAuthorizationTask.AddOnCompleteListener(
new OnCompleteListener
(
this, "Cancel Authorization Success",
"Cancel Authorization Failed"
)
);
}
public void SignOut()
{
Task signOutTask = mAuthManager.SignOut();
signOutTask.AddOnSuccessListener(new OnSuccessListener(this, "SignOut Success"))
.AddOnFailureListener(new OnFailureListener("SignOut Failed"));
}
public class OnCompleteListener : Java.Lang.Object, IOnCompleteListener
{
//Message when task is successful
private string successMessage;
//Message when task is failed
private string failureMessage;
MainActivity context;
public OnCompleteListener(MainActivity context, string SuccessMessage, string FailureMessage)
{
this.context = context;
this.successMessage = SuccessMessage;
this.failureMessage = FailureMessage;
}
public void OnComplete(Task task)
{
if (task.IsSuccessful)
{
//do some thing while cancel success
Log.Info(TAG, successMessage);
//context.SignOut();
}
else
{
//do some thing while cancel failed
Exception exception = task.Exception;
if (exception is ApiException)
{
int statusCode = ((ApiException)exception).StatusCode;
Log.Info(TAG, failureMessage + ": " + statusCode);
}
//context.ManageHomeScreen(null, true);
}
}
}
public class OnSuccessListener : Java.Lang.Object, Com.Huawei.Hmf.Tasks.IOnSuccessListener
{
//Message when task is successful
private string successMessage;
MainActivity context;
public OnSuccessListener(MainActivity context, string SuccessMessage)
{
this.successMessage = SuccessMessage;
this.context = context;
}
public void OnSuccess(Java.Lang.Object p0)
{
Log.Info(TAG, successMessage);
Toast.MakeText(Android.App.Application.Context, successMessage, ToastLength.Short).Show();
context.ManageHomeScreen(null, false);
}
}
public class OnFailureListener : Java.Lang.Object, Com.Huawei.Hmf.Tasks.IOnFailureListener
{
//Message when task is failed
private string failureMessage;
public OnFailureListener(string FailureMessage)
{
this.failureMessage = FailureMessage;
}
public void OnFailure(Java.Lang.Exception p0)
{
Log.Info(TAG, failureMessage);
Toast.MakeText(Android.App.Application.Context, failureMessage, ToastLength.Short).Show();
}
}
public void CheckIfIAPAvailable()
{
IIapClient mClient = Iap.GetIapClient(this);
Task isEnvReady = mClient.IsEnvReady();
isEnvReady.AddOnSuccessListener(new ListenerImp(this)).AddOnFailureListener(new ListenerImp(this));
}
class ListenerImp : Java.Lang.Object, IOnSuccessListener, IOnFailureListener
{
private MainActivity mainActivity;
public ListenerImp(MainActivity mainActivity)
{
this.mainActivity = mainActivity;
}
public void OnSuccess(Java.Lang.Object IsEnvReadyResult)
{
// Obtain the execution result.
Intent intent = new Intent(mainActivity, typeof(ComapnyActivity));
mainActivity.StartActivity(intent);
}
public void OnFailure(Java.Lang.Exception e)
{
Toast.MakeText(Android.App.Application.Context, "Feature Not available for your country", ToastLength.Short).Show();
if (e.GetType() == typeof(IapApiException))
{
IapApiException apiException = (IapApiException)e;
if (apiException.Status.StatusCode == OrderStatusCode.OrderHwidNotLogin)
{
// Not logged in.
//Call StartResolutionForResult to bring up the login page
}
else if (apiException.Status.StatusCode == OrderStatusCode.OrderAccountAreaNotSupported)
{
// The current region does not support HUAWEI IAP.
}
}
}
}
}
}
CompanyActivity.cs
This activity performs all the operation In-App purchasing and display list of company with package details.
Java:
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Support.V7.App;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Views;
using Android.Widget;
using Com.Huawei.Hmf.Tasks;
using Com.Huawei.Hms.Iap;
using Com.Huawei.Hms.Iap.Entity;
using Org.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PlacementApp
{
[Activity(Label = "ComapnyActivity", Theme = "@style/AppTheme")]
public class ComapnyActivity : AppCompatActivity, BuyProduct
{
private static String TAG = "ComapnyActivity";
private RecyclerView recyclerView;
private CompanyAdapter adapter;
IList<ProductInfo> productList;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
SetContentView(Resource.Layout.activity_company);
recyclerView = FindViewById<RecyclerView>(Resource.Id.recyclerview);
recyclerView.SetLayoutManager(new LinearLayoutManager(this));
recyclerView.SetItemAnimator(new DefaultItemAnimator());
//ADAPTER
adapter = new CompanyAdapter(this);
adapter.SetData(productList);
recyclerView.SetAdapter(adapter);
GetProducts();
}
private void GetProducts()
{
List<String> productIdList = new List<String>();
productIdList.Add("Nokia");
productIdList.Add("Hyperlink");
productIdList.Add("Tata");
productIdList.Add("Infosys");
productIdList.Add("Wipro");
ProductInfoReq req = new ProductInfoReq();
// PriceType: 0: consumable; 1: non-consumable; 2: auto-renewable subscription
req.PriceType = 0;
req.ProductIds = productIdList;
//"this" in the code is a reference to the current activity
Task task = Iap.GetIapClient(this).ObtainProductInfo(req);
task.AddOnSuccessListener(new QueryProductListenerImp(this)).AddOnFailureListener(new QueryProductListenerImp(this));
}
class QueryProductListenerImp : Java.Lang.Object, IOnSuccessListener, IOnFailureListener
{
private ComapnyActivity activity;
public QueryProductListenerImp(ComapnyActivity activity)
{
this.activity = activity;
}
public void OnSuccess(Java.Lang.Object result)
{
// Obtain the result
ProductInfoResult productlistwrapper = (ProductInfoResult)result;
IList<ProductInfo> productList = productlistwrapper.ProductInfoList;
activity.adapter.SetData(productList);
activity.adapter.NotifyDataSetChanged();
}
public void OnFailure(Java.Lang.Exception e)
{
//get the status code and handle the error
}
}
public void OnBuyProduct(ProductInfo pInfo)
{
//Toast.MakeText(Android.App.Application.Context, pInfo.ProductName, ToastLength.Short).Show();
CreatePurchaseRequest(pInfo);
}
private void CreatePurchaseRequest(ProductInfo pInfo)
{
// Constructs a PurchaseIntentReq object.
PurchaseIntentReq req = new PurchaseIntentReq();
// The product ID is the same as that set by a developer when configuring product information in AppGallery Connect.
// PriceType: 0: consumable; 1: non-consumable; 2: auto-renewable subscription
req.PriceType = pInfo.PriceType;
req.ProductId = pInfo.ProductId;
//"this" in the code is a reference to the current activity
Task task = Iap.GetIapClient(this).CreatePurchaseIntent(req);
task.AddOnSuccessListener(new BuyListenerImp(this)).AddOnFailureListener(new BuyListenerImp(this));
}
protected override void OnActivityResult(int requestCode, Android.App.Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == 6666)
{
if (data == null)
{
Log.Error(TAG, "data is null");
return;
}
//"this" in the code is a reference to the current activity
PurchaseResultInfo purchaseIntentResult = Iap.GetIapClient(this).ParsePurchaseResultInfoFromIntent(data);
switch (purchaseIntentResult.ReturnCode)
{
case OrderStatusCode.OrderStateCancel:
// User cancel payment.
Toast.MakeText(Android.App.Application.Context, "Payment Cancelled", ToastLength.Short).Show();
break;
case OrderStatusCode.OrderStateFailed:
Toast.MakeText(Android.App.Application.Context, "Order Failed", ToastLength.Short).Show();
break;
case OrderStatusCode.OrderProductOwned:
// check if there exists undelivered products.
Toast.MakeText(Android.App.Application.Context, "Undelivered Products", ToastLength.Short).Show();
break;
case OrderStatusCode.OrderStateSuccess:
// pay success.
Toast.MakeText(Android.App.Application.Context, "Payment Success", ToastLength.Short).Show();
// use the public key of your app to verify the signature.
// If ok, you can deliver your products.
// If the user purchased a consumable product, call the ConsumeOwnedPurchase API to consume it after successfully delivering the product.
String inAppPurchaseDataStr = purchaseIntentResult.InAppPurchaseData;
MakeProductReconsumeable(inAppPurchaseDataStr);
break;
default:
break;
}
return;
}
}
private void MakeProductReconsumeable(String InAppPurchaseDataStr)
{
String purchaseToken = null;
try
{
InAppPurchaseData InAppPurchaseDataBean = new InAppPurchaseData(InAppPurchaseDataStr);
if (InAppPurchaseDataBean.PurchaseStatus != InAppPurchaseData.PurchaseState.Purchased)
{
return;
}
purchaseToken = InAppPurchaseDataBean.PurchaseToken;
}
catch (JSONException e) { }
ConsumeOwnedPurchaseReq req = new ConsumeOwnedPurchaseReq();
req.PurchaseToken = purchaseToken;
//"this" in the code is a reference to the current activity
Task task = Iap.GetIapClient(this).ConsumeOwnedPurchase(req);
task.AddOnSuccessListener(new ConsumListenerImp()).AddOnFailureListener(new ConsumListenerImp());
}
class ConsumListenerImp : Java.Lang.Object, IOnSuccessListener, IOnFailureListener
{
public void OnSuccess(Java.Lang.Object result)
{
// Obtain the result
Log.Info(TAG, "Product available for purchase");
}
public void OnFailure(Java.Lang.Exception e)
{
//get the status code and handle the error
Log.Info(TAG, "Product available for purchase API Failed");
}
}
class BuyListenerImp : Java.Lang.Object, IOnSuccessListener, IOnFailureListener
{
private ComapnyActivity activity;
public BuyListenerImp(ComapnyActivity activity)
{
this.activity = activity;
}
public void OnSuccess(Java.Lang.Object result)
{
// Obtain the payment result.
PurchaseIntentResult InResult = (PurchaseIntentResult)result;
if (InResult.Status != null)
{
// 6666 is an int constant defined by the developer.
InResult.Status.StartResolutionForResult(activity, 6666);
}
}
public void OnFailure(Java.Lang.Exception e)
{
//get the status code and handle the error
Toast.MakeText(Android.App.Application.Context, "Purchase Request Failed !", ToastLength.Short).Show();
}
}
}
}
Xamarin App Build Result
1. Navigate to Solution Explore > Project > Right Click > Archive/View Archive to generate SHA-256 for build release and Click on Distribute.
2. Choose Distribution Channel > Ad Hoc to sign apk.
3. Choose Demo Keystore to release apk.
4. Build succeed and Save apk file.
5. Finally here is the result.
Tips and Tricks
1. It is recommended that the app obtains the public payment key from your server in real-time. Do not store it on the app to prevent app version incompatibility caused by the subsequent key upgrade.
2. The sandbox testing function can be used only when the following conditions are met: A sandbox testing account is successfully added, and the value of versionCode of the test package is greater than that of the released package. In the HMS Core IAP SDK 4.0.2, the isSandboxActivated API is added to check whether the current sandbox testing environment is available. If not, the API returns the reason why the environment is unavailable.
Conclusion
In this article, we have learned how to integrate HMS In-App Purchase and Account Kit in Xamarin based Android application. Student can easily apply in a listed company which offers campus placement.
Be sure to like and comments on this article, if you found it helpful. It means a lot to me.
References
Document
developer.huawei.com
Document
developer.huawei.com
Read In Forum

Expert: Doctor Consult using RxAndroid and MVVM with Huawei Kits (Account, Map, Identity) in Android App

Overview
Click to expand...
Click to collapse
In this article, I will create a Doctor Consult Demo App along with the integration of Huawei Id, Map and Identity Kit. Which provides an easy interface to consult with doctor. Users can choose specific doctors and get the doctor details using Huawei User Address.
By reading this article, you'll get an overview of HMS Core Identity, Map and Account Kit, including its functions, open capabilities and business value.
HMS Core Map Service Introduction
HMS Core Map SDK is a set of APIs for map development in Android. The map data covers most countries outside China and supports multiple languages. The Map SDK uses the WGS 84 GPS coordinate system, which can meet most requirements of map development outside China. You can easily add map-related functions in your Android app, including:
Map display: Displays buildings, roads, water systems, and Points of Interest (POIs).
Map interaction: Controls the interaction gestures and buttons on the map.
Map drawing: Adds location markers, map layers, overlays, and various shapes.
Prerequisite
Huawei Phone EMUI 3.0 or later.
Non-Huawei phones Android 4.4 or later (API level 19 or higher).
Android Studio.
AppGallery Account.
App Gallery Integration process
Sign In and Create or Choose a project on AppGallery Connect portal.
Navigate to Project settings and download the configuration file.
Navigate to General Information, and then provide Data Storage location.
App Development
Create A New Project.
Configure Project Gradle.
Configure App Gradle.
apply plugin: 'com.android.application'apply plugin: 'com.huawei.agconnect'android { compileSdkVersion 30 buildToolsVersion "29.0.3" defaultConfig { applicationId "com.hms.doctorconsultdemo" minSdkVersion 27 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }}dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.cardview:cardview:1.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' //noinspection GradleCompatible implementation 'com.android.support:recyclerview-v7:27.0.2' implementation 'androidx.navigation:navigation-ui:2.1.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'com.huawei.hms:identity:5.3.0.300' //Dagger implementation 'com.google.dagger:dagger:2.13' annotationProcessor 'com.google.dagger:dagger-compiler:2.13' //noinspection GradleCompatible implementation 'com.android.support:cardview-v7:28.0.0' implementation 'com.android.support:support-v4:28.0.0' implementation 'com.google.android.material:material:1.2.0' implementation "com.google.code.gson:gson:2.8.5" implementation('com.huawei.hms:hwid:4.0.4.300') implementation "com.squareup.okhttp3khttp:3.14.2" implementation 'com.squareup.okiokio:1.14.1' implementation 'com.github.bumptech.glide:glide:4.9.0' implementation 'com.huawei.hms:ads-lite:13.4.30.307' implementation 'com.huawei.hms:hianalytics:5.0.3.300' //map// implementation 'com.huawei.hms:maps:4.0.0.301' implementation 'com.huawei.hms:maps:5.0.1.300' //site implementation 'com.huawei.hms:site:4.0.0.300' //location implementation 'com.huawei.hms:location:4.0.3.301' implementation 'com.squareup.retrofit2:retrofit:2.5.0' implementation 'com.squareup.retrofit2:converter-gson:2.5.0' implementation "com.squareup.retrofit2:adapter-rxjava2:2.6.2" implementation 'io.reactivex:rxjava:1.3.0' implementation 'io.reactivex:rxandroid:1.2.1' implementation 'com.android.support:multidex:1.0.3' implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2' // RxAndroid implementation 'io.reactivex.rxjava2:rxjava:2.2.8' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'com.huawei.hms:awareness:1.0.4.301' implementation 'com.huawei.hms:dtm-api:5.0.2.300' implementation 'com.huawei.agconnect:agconnect-remoteconfig:1.3.1.300' implementation 'com.huawei.agconnect:agconnect-crash:1.4.1.300' implementation "com.huawei.agconnect:agconnect-appmessaging:1.4.1.300" implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'}
Configure AndroidManifest.xml.
Create Activity class with XML UI.
DirectionActivity:
JavaScript:
package com.hms.doctorconsultdemo.map;
import android.Manifest;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.cardview.widget.CardView;
import androidx.core.app.ActivityCompat;
import androidx.lifecycle.ViewModelProviders;
import com.google.android.material.card.MaterialCardView;
import com.hms.doctorconsultdemo.BookAppointmentActivity;
import com.hms.doctorconsultdemo.R;
import com.hms.doctorconsultdemo.map.apiconnector.polylineBody.Destination;
import com.hms.doctorconsultdemo.map.apiconnector.polylineBody.Origin;
import com.hms.doctorconsultdemo.map.apiconnector.polylineBody.PolylineBody;
import com.hms.doctorconsultdemo.map.apiconnector.polylineResponse.Paths;
import com.hms.doctorconsultdemo.map.apiconnector.polylineResponse.Polyline;
import com.hms.doctorconsultdemo.map.apiconnector.polylineResponse.PolylineResponse;
import com.hms.doctorconsultdemo.map.apiconnector.polylineResponse.Routes;
import com.hms.doctorconsultdemo.map.apiconnector.polylineResponse.Steps;
import com.huawei.agconnect.remoteconfig.AGConnectConfig;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hms.common.ApiException;
import com.huawei.hms.common.ResolvableApiException;
import com.huawei.hms.location.FusedLocationProviderClient;
import com.huawei.hms.location.LocationAvailability;
import com.huawei.hms.location.LocationCallback;
import com.huawei.hms.location.LocationRequest;
import com.huawei.hms.location.LocationResult;
import com.huawei.hms.location.LocationServices;
import com.huawei.hms.location.LocationSettingsRequest;
import com.huawei.hms.location.LocationSettingsStatusCodes;
import com.huawei.hms.location.SettingsClient;
import com.huawei.hms.maps.CameraUpdateFactory;
import com.huawei.hms.maps.HuaweiMap;
import com.huawei.hms.maps.MapView;
import com.huawei.hms.maps.OnMapReadyCallback;
import com.huawei.hms.maps.SupportMapFragment;
import com.huawei.hms.maps.model.LatLng;
import com.huawei.hms.maps.model.MapStyleOptions;
import com.huawei.hms.maps.model.Marker;
import com.huawei.hms.maps.model.MarkerOptions;
import com.huawei.hms.maps.model.PolylineOptions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DirectionActivity extends AppCompatActivity implements OnMapReadyCallback {
public static final String TAG = "DirectionActivity";
private static final String MAPVIEW_BUNDLE_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
private HuaweiMap hmap;
private MapView mMapView;
private Marker mMarker;
private List<LatLng> latLngList;
private MapApiViewModel mapApiViewModel;
private CardView cardView;
private LocationCallback mLocationCallback;
private LocationRequest mLocationRequest;
private FusedLocationProviderClient fusedLocationProviderClient;
private SettingsClient settingsClient;
private PolylineBody polylineBody;
private Button btnBooking;
private Map<String, Object> remoteConfigMap;
private AGConnectConfig config;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
init();
remoteConfigMap = new HashMap<>();
config = AGConnectConfig.getInstance();
remoteConfigMap.put("mapstyle", "light");
config.applyDefault(remoteConfigMap);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.mapView);
mMapView = findViewById(R.id.mapView);
Bundle mapViewBundle = null;
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY);
}
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(DirectionActivity.this);
}
private void init() {
setContentView(R.layout.activity_direction);
cardView = findViewById(R.id.card_map);
btnBooking = findViewById(R.id.btn_book_trip);
btnBooking.setOnClickListener(view -> {
Intent intent = new Intent(this, BookAppointmentActivity.class);
startActivity(intent);
});
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
Bundle extras = getIntent().getExtras();
if (extras != null) {
String name = extras.getString("name");
String orgLat = extras.getString("orgLat");
String orgLong = extras.getString("orgLong");
String desLat = extras.getString("desLat");
String desLong = extras.getString("desLong");
boolean tripDisplay = extras.getBoolean("isTrip");
if (!tripDisplay) {
cardView.setVisibility(View.GONE);
} else {
cardView.setVisibility(View.VISIBLE);
}
setTitle(name);
setLatLong(orgLat, orgLong, desLat, desLong);
}
mapApiViewModel = ViewModelProviders.of(this).get(MapApiViewModel.class);
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
settingsClient = LocationServices.getSettingsClient(this);
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(10000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
if (null == mLocationCallback) {
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult != null) {
List<Location> locations = locationResult.getLocations();
if (!locations.isEmpty()) {
for (Location location : locations) {
Log.i(TAG,
"onLocationResult location[Longitude,Latitude,Accuracy]:" + location.getLongitude()
+ "," + location.getLatitude() + "," + location.getAccuracy());
}
}
}
}
@Override
public void onLocationAvailability(LocationAvailability locationAvailability) {
if (locationAvailability != null) {
boolean flag = locationAvailability.isLocationAvailable();
Log.i(TAG, TAG + flag);
}
}
};
}
// check location permisiion
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
Log.i(TAG, "sdk < 28 Q");
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
String[] strings =
{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
ActivityCompat.requestPermissions(this, strings, 1);
}
} else {
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this,
"android.permission.ACCESS_BACKGROUND_LOCATION") != PackageManager.PERMISSION_GRANTED) {
String[] strings = {Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
"android.permission.ACCESS_BACKGROUND_LOCATION"};
ActivityCompat.requestPermissions(this, strings, 2);
}
}
}
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
protected void onResume() {
super.onResume();
mMapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
mMapView.onDestroy();
}
@Override
public void onMapReady(HuaweiMap map) {
hmap = map;
hmap.setMyLocationEnabled(true);
hmap.setTrafficEnabled(true);
hmap.getUiSettings().setRotateGesturesEnabled(true);
hmap.getUiSettings().setCompassEnabled(false);
mapApiViewModel.getPolylineLiveData(getPolylineBody()).observe(this, result -> {
Log.d(TAG, result.toString());
getPolylineData(result);
});
addHMSRemoteConfigListner();
}
private PolylineBody getPolylineBody() {
return polylineBody;
}
private void setLatLong(String orgLat, String orgLong, String desLat, String desLong) {
polylineBody = new PolylineBody();
Origin origin = new Origin();
origin.setLat(orgLat);
origin.setLng(orgLong);
Destination destination = new Destination();
destination.setLat(desLat);
destination.setLng(desLong);
polylineBody.setDestination(destination);
polylineBody.setOrigin(origin);
}
public void getPolylineData(PolylineResponse polylineResponse) {
List<Routes> routesList = polylineResponse.getRoutes();
List<Paths> paths = new ArrayList<>();
List<Steps> steps = new ArrayList<>();
List<Polyline> polylines = new ArrayList<>();
latLngList = new ArrayList<>();
for (int x = 0; x < routesList.size(); x++) {
for (Paths paths1 : routesList.get(x).getPaths()) {
paths.add(paths1);
}
for (int y = 0; y < paths.size(); y++) {
for (Steps step :
paths.get(y).getSteps()) {
steps.add(step);
}
}
for (int i = 0; i < steps.size(); i++) {
for (Polyline polyline :
steps.get(i).getPolyline()) {
polylines.add(polyline);
}
}
}
for (int i = 0; i < polylines.size(); i++) {
latLngList.add(new LatLng(Double.valueOf(polylines.get(i).getLat())
, Double.valueOf(polylines.get(i).getLng())));
}
hmap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLngList.get(0), 12.0f));
hmap.addMarker(new MarkerOptions().position(latLngList.get(0)));
hmap.addPolyline(new PolylineOptions()
.addAll(latLngList)
.color(Color.BLUE)
.width(3));
}
private void requestLocationUpdatesWithCallback() {
try {
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
LocationSettingsRequest locationSettingsRequest = builder.build();
settingsClient.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener(locationSettingsResponse -> {
Log.i(TAG, "check location settings success");
fusedLocationProviderClient
.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper())
.addOnSuccessListener(aVoid -> Log.i(TAG, "requestLocationUpdatesWithCallback onSuccess"))
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG,
"requestLocationUpdatesWithCallback onFailure:" + e.getMessage());
}
});
})
.addOnFailureListener(e -> {
Log.e(TAG, "checkLocationSetting onFailure:" + e.getMessage());
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
try {
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(DirectionActivity.this, 0);
} catch (IntentSender.SendIntentException sie) {
Log.e(TAG, "PendingIntent unable to execute request.");
}
break;
}
});
} catch (Exception e) {
Log.e(TAG, "requestLocationUpdatesWithCallback exception:" + e.getMessage());
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (grantResults.length > 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "onRequestPermissionsResult: apply LOCATION PERMISSION successful");
} else {
Log.i(TAG, "onRequestPermissionsResult: apply LOCATION PERMISSSION failed");
}
}
if (requestCode == 2) {
if (grantResults.length > 2 && grantResults[2] == PackageManager.PERMISSION_GRANTED
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "onRequestPermissionsResult: apply ACCESS_BACKGROUND_LOCATION successful");
} else {
Log.i(TAG, "onRequestPermissionsResult: apply ACCESS_BACKGROUND_LOCATION failed");
}
}
}
private void addHMSRemoteConfigListner() {
config.fetch(5).addOnSuccessListener(configValues -> {
config.apply(configValues);
MapStyleOptions mapStyleOptions;
String style = config.getValueAsString("mapstyle");
String colorPrimary = config.getValueAsString("primarycolor");
Log.d(TAG, "HMS color : " + colorPrimary);
ActionBar actionBar = getSupportActionBar();
actionBar.setBackgroundDrawable(new ColorDrawable(Color.parseColor(colorPrimary)));
if (style.equalsIgnoreCase("dark")) {
mapStyleOptions = MapStyleOptions.loadRawResourceStyle(DirectionActivity.this, R.raw.mapstyle_night);
hmap.setMapStyle(mapStyleOptions);
} else if (style.equalsIgnoreCase("light")) {
mapStyleOptions = MapStyleOptions.loadRawResourceStyle(DirectionActivity.this, R.raw.mapstyle_day);
hmap.setMapStyle(mapStyleOptions);
}
}).addOnFailureListener((OnFailureListener) e -> {
Log.d(TAG, e.getMessage());
});
}
}
App Build Result
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Tips and Tricks
Map data of Map Kit does not cover the Chinese mainland. Therefore, the Map SDK for Android, Map SDK (Java) for HarmonyOS, JavaScript API, Static Map API, and Directions API of Map Kit are unavailable in the Chinese mainland. For details about locations where the services are available.
The map zoom icons flash on the map on devices running Android 8 or earlier before the map is loaded. (This issue occurs at a low probability in Android 8, but does not occur in versions later than Android 8.)
Layout file (XML file): Set uiZoomControls to false.
Code file: Set the parameter of the HuaweiMapOptions.zoomControlsEnabled(boolean isZoomControlsEnabled) method to false.
Conclusion
In this article, we have learned how to integrate HMS Core Identity and Map in Android application. After completely read this article user can easily implement Huawei User Address and Map APIs by HMS Core Identity, so that User can consult with doctor using Huawei User Address and redirect to Doctor Location.
Thanks for reading this article. Be sure to like and comment to this article, if you found it helpful. It means a lot to me.
References
HMS Identity Docs: https://developer.huawei.com/consumer/en/hms/huawei-identitykit/
https://developer.huawei.com/consum...des/android-sdk-introduction-0000001061991291
HMS Training Videos -
https://developer.huawei.com/consumer/en/training/

Expert: Directory App MVVM Jetpack (Video Call with Webrtc & HMS CloudDB) in Android using Kotlin- Part-5

Overview
In this article, I will create a Directory android application using Webrtc Video Calling App in which I will integrate HMS Core kits such as HMS Account, AuthService, Identity Kit, Firebase Auth, Firebase Realtime DB and CloudDB .
App will make use of android MVVM clean architecture using Jetpack components such as DataBinding, AndroidViewModel, Observer, LiveData and much more.
In this article we are going to implement DataBinding using Observable pattern.
Huawei Cloud DB Kit Introduction
Huawei Cloud DB is a device-cloud synergy database product that enables seamless data synchronization between the device and cloud and between devices, and supports offline application operations, helping you quickly develop device-cloud and multi-device synergy applications.
Flexible synchronization modes:
Cloud DB supports cache and local data synchronization modes. In cache mode, data on the device is a subset of data on the cloud. If persistent cache is allowed, query results will be automatically cached on the device. In local mode, data is stored locally and is not synchronized to the cloud.
Powerful query capability:
Cloud DB supports various predicate query methods. Multiple chain filtering conditions can be used to filter and sort returned results, and limit the number of objects in the returned result set. In cache mode, data can be queried from the Cloud DB zone on the cloud or that on the local device. In local mode, data is directly queried from the Cloud DB zone on the local device.
Real-time update:
In cache mode of Cloud DB, you can listen on data as needed and use the data synchronization function of Cloud DB to update changed data between the device and cloud in real time.
Offline operations:
In cache mode of Cloud DB, if persistent cache is allowed, the application query is automatically changed from Cloud DB to the local host after the device gets offline. All data modified locally will be automatically synchronized to Cloud DB after the device gets online.
Scalability:
Cloud DB provides powerful HUAWEI CLOUD infrastructure functions, including automatic multi-region data replication, consistency assurance, atomic batch operations, and transaction support.
Security level:
Cloud DB supports device-cloud data full encryption management, triple authentication by app, user, and service, and role-based permission management to ensure data security.
WebRTC Service Introduction
WebRTC is a free and open-source project providing web browsers and mobile applications with real-time communication via application programming interfaces.
Prerequisite
Huawei Phone EMUI 3.0 or later.
Non-Huawei phones Android 4.4 or later (API level 19 or higher).
HMS Core APK 4.0.0.300 or later
Android Studio
AppGallery Account
App Gallery Integration process
1. Sign In and Create or Choose a project on AppGallery Connect portal.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
2. Navigate to Project settings and download the configuration file.
3. Navigate to General Information, and then provide Data Storage location.
4. Navigate to Build then Enable Cloud DB
5. Navigate to Cloud DB and Create DB:
App Development
Add Required Dependencies:
Launch Android studio and create a new project. Once the project is ready.
Add following dependency for HMS Kits
Code:
//HMS Kits
implementation 'com.huawei.agconnect:agconnect-core:1.5.0.300'
implementation 'com.huawei.hms:hwid:5.3.0.302'
implementation 'com.huawei.hms:identity:5.3.0.300'
implementation 'com.huawei.agconnect:agconnect-cloud-database:1.5.0.300'
//Google Firebase
implementation platform('com.google.firebase:firebase-bom:28.4.1')
implementation 'com.google.firebase:firebase-analytics'
implementation 'com.google.firebase:firebase-auth'
implementation 'com.google.firebase:firebase-database'
implementation 'com.google.android.gms:play-services-auth:19.2.0'
implementation 'com.airbnb.android:lottie:4.1.0'
implementation 'com.mikhaellopez:circularimageview:4.3.0'
implementation 'com.kaopiz:kprogresshud:1.2.0'
implementation 'com.google.android.gms:play-services-ads:20.4.0' implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
Navigate to the Gradle scripts folder and open build.gradle (project: app)
Code:
repositories {
google()
jcenter()
maven {url 'https://developer.huawei.com/repo/'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath 'com.huawei.agconnect:agcp:1.4.2.300'
Configure AndroidManifest.xml.
Code:
<meta-data
android:name="install_channel"
android:value="AppGallery" />
<meta-data
android:name="com.huawei.hms.client.appid"
android:value="104460711" />
</application>
Code Implementation
Created following package model, event, viewmodel.
ViewModel: The ViewModel makes it easy to update data changes on the UI.Create a package named viewmodel in your main folder.Then create a new file and name it LoginViewModel.kt along with their FactoryViewModelProviders.
MainActivity.kt:
Code:
package com.hms.directoryclass MainActivity : AppCompatActivity(), ActivityNavigation { private lateinit var viewModel: LoginViewModel
private lateinit var dataBinding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) val viewModel: LoginViewModel by lazy {
val activity = requireNotNull(this) {}
ViewModelProviders.of(this, LoginViewModelFactory(activity.application))
.get(LoginViewModel::class.java)
} dataBinding.loginViewModel = viewModel
dataBinding.lifecycleOwner = this
viewModel.startActivityForResultEvent.setEventReceiver(this, this)
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
viewModel.onResultFromActivity(requestCode, data)
super.onActivityResult(requestCode, resultCode, data)
}}
LoginViewModel.kt:
Code:
package com.hms.directory.viewmodel
@SuppressLint("StaticFieldLeak")
class LoginViewModel(application: Application) : AndroidViewModel(application), Observable { private val context = getApplication<Application>().applicationContext
private var mAuthManager: AccountAuthService? = null
private var mAuthParam: AccountAuthParams? = null val startActivityForResultEvent = LiveMessageEvent<ActivityNavigation>() fun login() {
val intent = Intent(context, OrderActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent) /* mAuthParam = AccountAuthParamsHelper(AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setIdToken()
.setAccessToken()
.createParams()
mAuthManager = AccountAuthManager.getService(Activity(), mAuthParam)
startActivityForResultEvent.sendEvent {
startActivityForResult(
mAuthManager?.signInIntent,
HMS_SIGN_IN
)
}*/
} fun onResultFromActivity(requestCode: Int, data: Intent?) {
when (requestCode) {
HMS_SIGN_IN -> {
val authAccountTask = AccountAuthManager.parseAuthResultFromIntent(data)
onCompleteLogin(authAccountTask)
}
}
} private fun onCompleteLogin(doneTask: Task<AuthAccount>) {
if (doneTask.isSuccessful) {
val authAccount = doneTask.result
Log.d("LoginViewModel", "SigIn Success")
context.startActivity(Intent(context, ContactListActivity::class.java)) } else {
Log.d("LoginViewModel", "SigIn Error")
}
} override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
} override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}}
ContactActivity.kt:
Code:
public class ContactListActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact_list); // Load contacts from file
Contacts.loadData(this); // Set up recycler view and fill it with all the contacts
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.contact_list);
recyclerView.setAdapter(new ContactListAdapter(this, Contacts.LIST)); }
LoginFireBaseActivity.java
Code:
package com.hms.directory.app.call;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;
import com.google.firebase.database.FirebaseDatabase;
import com.hms.corrierapp.R;
import com.hms.directory.app.call.models.User;import org.jetbrains.annotations.NotNull;public class LoginActivity extends AppCompatActivity { GoogleSignInClient mGoogleSignInClient;
int RC_SIGN_IN = 11;
FirebaseAuth mAuth;
FirebaseDatabase database; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_goole); mAuth = FirebaseAuth.getInstance();
if (mAuth.getCurrentUser() != null) {
goToNextActivity();
} database = FirebaseDatabase.getInstance(); GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken("1016048264402-439a9aamtpiajbgqeqg24qkum2bb7fmh.apps.googleusercontent.com")
.requestEmail()
.build(); mGoogleSignInClient = GoogleSignIn.getClient(this, gso); findViewById(R.id.loginBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = mGoogleSignInClient.getSignInIntent();
startActivityForResult(intent, RC_SIGN_IN);
//startActivity(new Intent(LoginActivity.this, MainActivity.class));
}
});
} void goToNextActivity() {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
} @Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable @org.jetbrains.annotations.Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data); if (requestCode == RC_SIGN_IN) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
GoogleSignInAccount account = task.getResult();
authWithGoogle(account.getIdToken());
}
} void authWithGoogle(String idToken) {
AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
mAuth.signInWithCredential(credential)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull @NotNull Task<AuthResult> task) {
if (task.isSuccessful()) {
FirebaseUser user = mAuth.getCurrentUser();
User firebaseUser = new User(user.getUid(), user.getDisplayName(), user.getPhotoUrl().toString(), "Unknown", 500);
database.getReference()
.child("profiles")
.child(user.getUid())
.setValue(firebaseUser).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull @NotNull Task<Void> task) {
if (task.isSuccessful()) {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finishAffinity();
} else {
Toast.makeText(LoginActivity.this, task.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
}
});
//Log.e("profile", user.getPhotoUrl().toString());
} else {
Log.e("err", task.getException().getLocalizedMessage());
}
}
});
}
}
CallConnectingActivity.java
Code:
public class ConnectingActivity extends AppCompatActivity { ActivityConnectingBinding binding;
FirebaseAuth auth;
FirebaseDatabase database;
boolean isOkay = false; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityConnectingBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); auth = FirebaseAuth.getInstance();
database = FirebaseDatabase.getInstance(); String profile = getIntent().getStringExtra("profile");
Glide.with(this)
.load(profile)
.into(binding.profile); String username = auth.getUid(); database.getReference().child("users")
.orderByChild("status")
.equalTo(0).limitToFirst(1)
.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull @NotNull DataSnapshot snapshot) {
if (snapshot.getChildrenCount() > 0) {
isOkay = true;
// Room Available
for (DataSnapshot childSnap : snapshot.getChildren()) {
database.getReference()
.child("users")
.child(childSnap.getKey())
.child("incoming")
.setValue(username);
database.getReference()
.child("users")
.child(childSnap.getKey())
.child("status")
.setValue(1);
Intent intent = new Intent(ConnectingActivity.this, CallActivity.class);
String incoming = childSnap.child("incoming").getValue(String.class);
String createdBy = childSnap.child("createdBy").getValue(String.class);
boolean isAvailable = childSnap.child("isAvailable").getValue(Boolean.class);
intent.putExtra("username", username);
intent.putExtra("incoming", incoming);
intent.putExtra("createdBy", createdBy);
intent.putExtra("isAvailable", isAvailable);
startActivity(intent);
finish();
}
} else {
// Not Available HashMap<String, Object> room = new HashMap<>();
room.put("incoming", username);
room.put("createdBy", username);
room.put("isAvailable", true);
room.put("status", 0); database.getReference()
.child("users")
.child(username)
.setValue(room).addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void unused) {
database.getReference()
.child("users")
.child(username).addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull @NotNull DataSnapshot snapshot) {
if (snapshot.child("status").exists()) {
if (snapshot.child("status").getValue(Integer.class) == 1) { if (isOkay)
return; isOkay = true;
Intent intent = new Intent(ConnectingActivity.this, CallActivity.class);
String incoming = snapshot.child("incoming").getValue(String.class);
String createdBy = snapshot.child("createdBy").getValue(String.class);
boolean isAvailable = snapshot.child("isAvailable").getValue(Boolean.class);
intent.putExtra("username", username);
intent.putExtra("incoming", incoming);
intent.putExtra("createdBy", createdBy);
intent.putExtra("isAvailable", isAvailable);
startActivity(intent);
finish();
}
}
} @Override
public void onCancelled(@NonNull @NotNull DatabaseError error) { }
});
}
}); }
} @Override
public void onCancelled(@NonNull @NotNull DatabaseError error) { }
});
}
}
CloudDB:
Code:
import android.content.Context;
import android.util.Log;import com.huawei.agconnect.AGCRoutePolicy;
import com.huawei.agconnect.AGConnectInstance;
import com.huawei.agconnect.AGConnectOptionsBuilder;
import com.huawei.agconnect.auth.AGConnectAuth;
import com.huawei.agconnect.cloud.database.AGConnectCloudDB;
import com.huawei.agconnect.cloud.database.CloudDBZone;
import com.huawei.agconnect.cloud.database.CloudDBZoneConfig;
import com.huawei.agconnect.cloud.database.CloudDBZoneQuery;
import com.huawei.agconnect.cloud.database.exceptions.AGConnectCloudDBException;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;import static android.content.ContentValues.TAG;public class CloudDB { private Context context;
private static CloudDB instance; private AGConnectCloudDB mCloudDB;
private CloudDBZoneConfig mConfig;
private CloudDBZone mCloudDBZone; private CloudDB(Context context) {
this.context=context;
} public static CloudDB getInstance(Context context) {
if (instance==null)instance=new CloudDB(context);
return instance;
} public CloudDB initAGConnectCloudDB() {
AGConnectCloudDB.initialize(context);
return this;
} public CloudDB createCloudDb(){ AGConnectInstance instance = AGConnectInstance.buildInstance(new AGConnectOptionsBuilder().setRoutePolicy(AGCRoutePolicy.CHINA).build(mContext));
mCloudDB = AGConnectCloudDB.getInstance(instance, AGConnectAuth.getInstance(instance));
mCloudDB.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo()); return this;
} public void configCloudDb(){
mConfig = new CloudDBZoneConfig("QuickStartDemo",
CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
mConfig.setPersistenceEnabled(true);
Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);
openDBZoneTask.addOnSuccessListener(new OnSuccessListener<CloudDBZone>() {
@Override
public void onSuccess(CloudDBZone cloudDBZone) {
Log.i(TAG, "open cloudDBZone success");
mCloudDBZone = cloudDBZone;
// Add subscription after opening cloudDBZone success
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.w(TAG, "open cloudDBZone failed for " + e.getMessage());
}
});
} public void upsertBookInfos(BookInfo bookInfo) {
if (mCloudDBZone == null) {
Log.w(TAG, "CloudDBZone is null, try re-open it");
return;
}
Task<Integer> upsertTask = mCloudDBZone.executeUpsert(bookInfo);
upsertTask.addOnSuccessListener(new OnSuccessListener<Integer>() {
@Override
public void onSuccess(Integer cloudDBZoneResult) {
Log.i(TAG, "Upsert " + cloudDBZoneResult + " records");
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
mUiCallBack.updateUiOnError("Insert book info failed");
}
});
} public void viewCloudDbData(){
private OnSnapshotListener<BookInfo> mSnapshotListener = new OnSnapshotListener<BookInfo>() {
@Override
public void onSnapshot(CloudDBZoneSnapshot<BookInfo> cloudDBZoneSnapshot, AGConnectCloudDBException e) {
if (e != null) {
Log.w(TAG, "onSnapshot: " + e.getMessage());
return;
}
CloudDBZoneObjectList<BookInfo> snapshotObjects = cloudDBZoneSnapshot.getSnapshotObjects();
List<BookInfo> bookInfos = new ArrayList<>();
try {
if (snapshotObjects != null) {
while (snapshotObjects.hasNext()) {
BookInfo bookInfo = snapshotObjects.next();
bookInfos.add(bookInfo);
updateBookIndex(bookInfo);
}
}
mUiCallBack.onSubscribe(bookInfos);
} catch (AGConnectCloudDBException snapshotException) {
Log.w(TAG, "onSnapshot:(getObject) " + snapshotException.getMessage());
} finally {
cloudDBZoneSnapshot.release();
}
}
};
} public void addSubscription() {
if (mCloudDBZone == null) {
Log.w(TAG, "CloudDBZone is null, try re-open it");
return;
} try {
CloudDBZoneQuery<BookInfo> snapshotQuery = CloudDBZoneQuery.where(BookInfo.class)
.equalTo(BookEditFields.SHADOW_FLAG, true);
mRegister = mCloudDBZone.subscribeSnapshot(snapshotQuery,
CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY, mSnapshotListener);
} catch (AGConnectCloudDBException e) {
Log.w(TAG, "subscribeSnapshot: " + e.getMessage());
}
}
}
Xml layout DataBinding
To include data binding in the UI, enclose all content with <layout></layout>.
The ViewModel is introduced to the layout in the <data></data> section, as shown. Ensure that the type value points to the specific folder that has the required ViewModel.
App Build Result
Tips and Tricks
Identity Kit displays the HUAWEI ID registration or sign-in page first. The user can use the functions provided by Identity Kit only after signing in using a registered HUAWEI ID.
Conclusion
In this article, we have learned how to integrate Huawei Cloud DB using Webrtc Video Call in Android application. After completely read this article user can easily implement Huawei CloudDB in the Directory App android application using Kotlin.
Thanks for reading this article. Be sure to like and comment to this article, if you found it helpful. It means a lot to me.
References
HMS Docs:
https://developer.huawei.com/consum.../HMSCore-Guides/introduction-0000001050048870
https://developer.huawei.com/consum...des/agc-clouddb-introduction-0000001054212760
Cloud DB Kit Training Video:
https://developer.huawei.com/consumer/en/training/course/video/101628259491513909

Expert: Directory App MVVM Jetpack (Video Call with Webrtc & HMS Analytics and Crash Kit) in Android using Kotlin- Part-6

Overview
In this article, I will create a Directory android application using Webrtc Video Calling App in which I will integrate HMS Core kits such as HMS Account, AuthService, Identity Kit, Firebase Auth, Firebase Realtime DB and CloudDB .
App will make use of android MVVM clean architecture using Jetpack components such as DataBinding, AndroidViewModel, Observer, LiveData and much more.
In this article we are going to implement DataBinding using Observable pattern.
Huawei Analytics Kit Introduction
Huawei Analytics Kit is a one-stop user behavior analysis platform for products such as mobile apps, web apps, quick apps, quick games, and mini-programs. It offers scenario-specific data collection, management, analysis, and usage, helping enterprises achieve effective user acquisition, product optimization, precise operations, and business growth..
Flexible synchronization modes:
Cloud DB supports cache and local data synchronization modes. In cache mode, data on the device is a subset of data on the cloud. If persistent cache is allowed, query results will be automatically cached on the device. In local mode, data is stored locally and is not synchronized to the cloud.
Powerful query capability:
Cloud DB supports various predicate query methods. Multiple chain filtering conditions can be used to filter and sort returned results, and limit the number of objects in the returned result set. In cache mode, data can be queried from the Cloud DB zone on the cloud or that on the local device. In local mode, data is directly queried from the Cloud DB zone on the local device.
Real-time update:
In cache mode of Cloud DB, you can listen on data as needed and use the data synchronization function of Cloud DB to update changed data between the device and cloud in real time.
Offline operations:
In cache mode of Cloud DB, if persistent cache is allowed, the application query is automatically changed from Cloud DB to the local host after the device gets offline. All data modified locally will be automatically synchronized to Cloud DB after the device gets online.
Scalability:
Cloud DB provides powerful HUAWEI CLOUD infrastructure functions, including automatic multi-region data replication, consistency assurance, atomic batch operations, and transaction support.
Security level:
Cloud DB supports device-cloud data full encryption management, triple authentication by app, user, and service, and role-based permission management to ensure data security.
WebRTC Service Introduction
WebRTC is a free and open-source project providing web browsers and mobile applications with real-time communication via application programming interfaces.
Prerequisite
Huawei Phone EMUI 3.0 or later.
Non-Huawei phones Android 4.4 or later (API level 19 or higher).
HMS Core APK 4.0.0.300 or later
Android Studio
AppGallery Account
App Gallery Integration process
1. Sign In and Create or Choose a project on AppGallery Connect portal.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
2. Navigate to Project settings and download the configuration file.
3. Navigate to General Information, and then provide Data Storage location.
App Development
Add Required Dependencies:
Launch Android studio and create a new project. Once the project is ready.
Add following dependency for HMS Kits
Code:
//HMS Kits
implementation 'com.huawei.hms:hianalytics:5.0.3.300'
implementation 'com.huawei.agconnect:agconnect-crash:1.4.1.300'
implementation 'com.huawei.agconnect:agconnect-core:1.5.0.300'
implementation 'com.huawei.hms:hwid:5.3.0.302'
implementation 'com.huawei.hms:identity:5.3.0.300'
implementation 'com.huawei.agconnect:agconnect-cloud-database:1.5.0.300'Copy codeCopy code//Google Firebase
implementation platform('com.google.firebase:firebase-bom:28.4.1')
implementation 'com.google.firebase:firebase-analytics'
implementation 'com.google.firebase:firebase-auth'
implementation 'com.google.firebase:firebase-database'
implementation 'com.google.android.gms:play-services-auth:19.2.0'
implementation 'com.airbnb.android:lottie:4.1.0'
implementation 'com.mikhaellopez:circularimageview:4.3.0'
implementation 'com.kaopiz:kprogresshud:1.2.0'
implementation 'com.google.android.gms:play-services-ads:20.4.0' implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
Navigate to the Gradle scripts folder and open build.gradle (project: app)
Code:
repositories {
google()
jcenter()
maven {url 'https://developer.huawei.com/repo/'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath 'com.huawei.agconnect:agcp:1.4.2.300'
Configure AndroidManifest.xml.
Code:
<meta-data
android:name="install_channel"
android:value="AppGallery" />
<meta-data
android:name="com.huawei.hms.client.appid"
android:value="104460711" />
</application>
Code Implementation
Created following package model, event, viewmodel.
ViewModel: The ViewModel makes it easy to update data changes on the UI.Create a package named viewmodel in your main folder.Then create a new file and name it LoginViewModel.kt along with their FactoryViewModelProviders.
MainActivity.kt:
Code:
package com.hms.directoryclass MainActivity : AppCompatActivity(), ActivityNavigation { private lateinit var viewModel: LoginViewModel
private lateinit var dataBinding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) val viewModel: LoginViewModel by lazy {
val activity = requireNotNull(this) {}
ViewModelProviders.of(this, LoginViewModelFactory(activity.application))
.get(LoginViewModel::class.java)
} dataBinding.loginViewModel = viewModel
dataBinding.lifecycleOwner = this
viewModel.startActivityForResultEvent.setEventReceiver(this, this)
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
viewModel.onResultFromActivity(requestCode, data)
super.onActivityResult(requestCode, resultCode, data)
}}
LoginViewModel.kt:
Code:
package com.hms.directory.viewmodel
@SuppressLint("StaticFieldLeak")
class LoginViewModel(application: Application) : AndroidViewModel(application), Observable { private val context = getApplication<Application>().applicationContext
private var mAuthManager: AccountAuthService? = null
private var mAuthParam: AccountAuthParams? = null val startActivityForResultEvent = LiveMessageEvent<ActivityNavigation>() fun login() {
val intent = Intent(context, OrderActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent) /* mAuthParam = AccountAuthParamsHelper(AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setIdToken()
.setAccessToken()
.createParams()
mAuthManager = AccountAuthManager.getService(Activity(), mAuthParam)
startActivityForResultEvent.sendEvent {
startActivityForResult(
mAuthManager?.signInIntent,
HMS_SIGN_IN
)
}*/
} fun onResultFromActivity(requestCode: Int, data: Intent?) {
when (requestCode) {
HMS_SIGN_IN -> {
val authAccountTask = AccountAuthManager.parseAuthResultFromIntent(data)
onCompleteLogin(authAccountTask)
}
}
} private fun onCompleteLogin(doneTask: Task<AuthAccount>) {
if (doneTask.isSuccessful) {
val authAccount = doneTask.result
Log.d("LoginViewModel", "SigIn Success")
context.startActivity(Intent(context, ContactListActivity::class.java)) } else {
Log.d("LoginViewModel", "SigIn Error")
}
} override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
} override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}}
ContactActivity.kt:
Code:
public class ContactListActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact_list); // Load contacts from file
Contacts.loadData(this); // Set up recycler view and fill it with all the contacts
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.contact_list);
recyclerView.setAdapter(new ContactListAdapter(this, Contacts.LIST));
AGConnectCrash.getInstance().enableCrashCollection(false);
//Crash application
AGConnectCrash.getInstance().testIt(this)
}
LoginFireBaseActivity.java
Code:
package com.hms.directory.app.call;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;
import com.google.firebase.database.FirebaseDatabase;
import com.hms.corrierapp.R;
import com.hms.directory.app.call.models.User;import org.jetbrains.annotations.NotNull;public class LoginActivity extends AppCompatActivity { GoogleSignInClient mGoogleSignInClient;
int RC_SIGN_IN = 11;
FirebaseAuth mAuth;
FirebaseDatabase database; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_goole); mAuth = FirebaseAuth.getInstance();
if (mAuth.getCurrentUser() != null) {
goToNextActivity();
} database = FirebaseDatabase.getInstance(); GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken("1016048264402-439a9aamtpiajbgqeqg24qkum2bb7fmh.apps.googleusercontent.com")
.requestEmail()
.build(); mGoogleSignInClient = GoogleSignIn.getClient(this, gso); findViewById(R.id.loginBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = mGoogleSignInClient.getSignInIntent(); startActivityForResult(intent, RC_SIGN_IN);
//startActivity(new Intent(LoginActivity.this, MainActivity.class));
}
});
} void goToNextActivity() {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
} @Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable @org.jetbrains.annotations.Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data); if (requestCode == RC_SIGN_IN) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
GoogleSignInAccount account = task.getResult();
authWithGoogle(account.getIdToken());
}
} void authWithGoogle(String idToken) {
AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
mAuth.signInWithCredential(credential)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull @NotNull Task<AuthResult> task) {
if (task.isSuccessful()) {
FirebaseUser user = mAuth.getCurrentUser();
User firebaseUser = new User(user.getUid(), user.getDisplayName(), user.getPhotoUrl().toString(), "Unknown", 500);
database.getReference()
.child("profiles")
.child(user.getUid())
.setValue(firebaseUser).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull @NotNull Task<Void> task) {
if (task.isSuccessful()) {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finishAffinity();
} else {
Toast.makeText(LoginActivity.this, task.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
}
});
//Log.e("profile", user.getPhotoUrl().toString());
} else {
Log.e("err", task.getException().getLocalizedMessage());
}
}
});
}
}
CallConnectingActivity.java
Code:
public class ConnectingActivity extends AppCompatActivity { ActivityConnectingBinding binding;
FirebaseAuth auth;
FirebaseDatabase database;
boolean isOkay = false; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityConnectingBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); auth = FirebaseAuth.getInstance();
database = FirebaseDatabase.getInstance(); String profile = getIntent().getStringExtra("profile");
Glide.with(this)
.load(profile)
.into(binding.profile); String username = auth.getUid(); database.getReference().child("users")
.orderByChild("status")
.equalTo(0).limitToFirst(1)
.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull @NotNull DataSnapshot snapshot) {
if (snapshot.getChildrenCount() > 0) {
isOkay = true;
// Room Available
for (DataSnapshot childSnap : snapshot.getChildren()) {
database.getReference()
.child("users")
.child(childSnap.getKey())
.child("incoming")
.setValue(username);
database.getReference()
.child("users")
.child(childSnap.getKey())
.child("status")
.setValue(1);
Intent intent = new Intent(ConnectingActivity.this, CallActivity.class);
String incoming = childSnap.child("incoming").getValue(String.class);
String createdBy = childSnap.child("createdBy").getValue(String.class);
boolean isAvailable = childSnap.child("isAvailable").getValue(Boolean.class);
intent.putExtra("username", username);
intent.putExtra("incoming", incoming);
intent.putExtra("createdBy", createdBy);
intent.putExtra("isAvailable", isAvailable);
startActivity(intent);
finish();
}
} else {
// Not Available HashMap<String, Object> room = new HashMap<>();
room.put("incoming", username);
room.put("createdBy", username);
room.put("isAvailable", true);
room.put("status", 0); database.getReference()
.child("users")
.child(username)
.setValue(room).addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void unused) {
database.getReference()
.child("users")
.child(username).addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull @NotNull DataSnapshot snapshot) {
if (snapshot.child("status").exists()) {
if (snapshot.child("status").getValue(Integer.class) == 1) { if (isOkay)
return; isOkay = true;
Intent intent = new Intent(ConnectingActivity.this, CallActivity.class);
String incoming = snapshot.child("incoming").getValue(String.class);
String createdBy = snapshot.child("createdBy").getValue(String.class);
boolean isAvailable = snapshot.child("isAvailable").getValue(Boolean.class);
intent.putExtra("username", username);
intent.putExtra("incoming", incoming);
intent.putExtra("createdBy", createdBy);
intent.putExtra("isAvailable", isAvailable);
startActivity(intent);
finish();
}
}
} @Override
public void onCancelled(@NonNull @NotNull DatabaseError error) { }
});
}
}); }
} @Override
public void onCancelled(@NonNull @NotNull DatabaseError error) { }
});
}
}
CloudDB:
Code:
import android.content.Context;
import android.util.Log;import com.huawei.agconnect.AGCRoutePolicy;
import com.huawei.agconnect.AGConnectInstance;
import com.huawei.agconnect.AGConnectOptionsBuilder;
import com.huawei.agconnect.auth.AGConnectAuth;
import com.huawei.agconnect.cloud.database.AGConnectCloudDB;
import com.huawei.agconnect.cloud.database.CloudDBZone;
import com.huawei.agconnect.cloud.database.CloudDBZoneConfig;
import com.huawei.agconnect.cloud.database.CloudDBZoneQuery;
import com.huawei.agconnect.cloud.database.exceptions.AGConnectCloudDBException;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;import static android.content.ContentValues.TAG;public class CloudDB { private Context context;
private static CloudDB instance; private AGConnectCloudDB mCloudDB;
private CloudDBZoneConfig mConfig;
private CloudDBZone mCloudDBZone; private CloudDB(Context context) {
this.context=context;
} public static CloudDB getInstance(Context context) {
if (instance==null)instance=new CloudDB(context);
return instance;
} public CloudDB initAGConnectCloudDB() {
AGConnectCloudDB.initialize(context);
return this;
} public CloudDB createCloudDb(){ AGConnectInstance instance = AGConnectInstance.buildInstance(new AGConnectOptionsBuilder().setRoutePolicy(AGCRoutePolicy.CHINA).build(mContext));
mCloudDB = AGConnectCloudDB.getInstance(instance, AGConnectAuth.getInstance(instance));
mCloudDB.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo()); return this;
} public void configCloudDb(){
mConfig = new CloudDBZoneConfig("QuickStartDemo",
CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
mConfig.setPersistenceEnabled(true);
Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);
openDBZoneTask.addOnSuccessListener(new OnSuccessListener<CloudDBZone>() {
@Override
public void onSuccess(CloudDBZone cloudDBZone) {
Log.i(TAG, "open cloudDBZone success");
mCloudDBZone = cloudDBZone;
// Add subscription after opening cloudDBZone success
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.w(TAG, "open cloudDBZone failed for " + e.getMessage());
}
});
} public void upsertBookInfos(BookInfo bookInfo) {
if (mCloudDBZone == null) {
Log.w(TAG, "CloudDBZone is null, try re-open it");
return;
}
Task<Integer> upsertTask = mCloudDBZone.executeUpsert(bookInfo);
upsertTask.addOnSuccessListener(new OnSuccessListener<Integer>() {
@Override
public void onSuccess(Integer cloudDBZoneResult) {
Log.i(TAG, "Upsert " + cloudDBZoneResult + " records");
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
mUiCallBack.updateUiOnError("Insert book info failed");
}
});
} public void viewCloudDbData(){
private OnSnapshotListener<BookInfo> mSnapshotListener = new OnSnapshotListener<BookInfo>() {
@Override
public void onSnapshot(CloudDBZoneSnapshot<BookInfo> cloudDBZoneSnapshot, AGConnectCloudDBException e) {
if (e != null) {
Log.w(TAG, "onSnapshot: " + e.getMessage());
return;
}
CloudDBZoneObjectList<BookInfo> snapshotObjects = cloudDBZoneSnapshot.getSnapshotObjects();
List<BookInfo> bookInfos = new ArrayList<>();
try {
if (snapshotObjects != null) {
while (snapshotObjects.hasNext()) {
BookInfo bookInfo = snapshotObjects.next();
bookInfos.add(bookInfo);
updateBookIndex(bookInfo);
}
}
mUiCallBack.onSubscribe(bookInfos);
} catch (AGConnectCloudDBException snapshotException) {
Log.w(TAG, "onSnapshot:(getObject) " + snapshotException.getMessage());
} finally {
cloudDBZoneSnapshot.release();
}
}
};
} public void addSubscription() {
if (mCloudDBZone == null) {
Log.w(TAG, "CloudDBZone is null, try re-open it");
return;
} try {
CloudDBZoneQuery<BookInfo> snapshotQuery = CloudDBZoneQuery.where(BookInfo.class)
.equalTo(BookEditFields.SHADOW_FLAG, true);
mRegister = mCloudDBZone.subscribeSnapshot(snapshotQuery,
CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY, mSnapshotListener);
} catch (AGConnectCloudDBException e) {
Log.w(TAG, "subscribeSnapshot: " + e.getMessage());
}
}
}
Xml layout DataBinding
To include data binding in the UI, enclose all content with <layout></layout>.
The ViewModel is introduced to the layout in the <data></data> section, as shown. Ensure that the type value points to the specific folder that has the required ViewModel.
App Build Result
Tips and Tricks
Identity Kit displays the HUAWEI ID registration or sign-in page first. The user can use the functions provided by Identity Kit only after signing in using a registered HUAWEI ID.
Conclusion
In this article, we have learned how to integrate Huawei Cloud DB using Webrtc Video Call in Android application. After completely read this article user can easily implement Huawei Crash Kit in the Directory App android application using Kotlin.
Thanks for reading this article. Be sure to like and comment to this article, if you found it helpful. It means a lot to me.
References
HMS Docs:
https://developer.huawei.com/consum.../HMSCore-Guides/introduction-0000001050048870
https://developer.huawei.com/consum...des/agc-clouddb-introduction-0000001054212760

Categories

Resources