Securing Your apps with SafetyDetect - Huawei Developers

More information like this, you can visit HUAWEI Developer Forum​
What is SafetyDetect?
SafetyDetect is an API or Kits that provide security checks to your application. This will help to protect your app against security threats or malicious programs.
The kits consists of 5 main APIs:
SysIntegrity API: Checks whether the device running your app is secure, for example, whether it is rooted.
AppsCheck API: Obtains a list of malicious apps.
URLCheck API: Determines the threat type of a specific URL.
UserDetect API: Checks whether your app is interacting with a fake user.
WifiDetect API: Checks whether the Wi-Fi to be connected is secure.
Why SafetyDetect?
Why do you want to use safetydetect?
Imagine that if your app deals with sensitive data and/or provide transactional capabilities, you would not want your app security to be compromised.
SafetyDetect will provide the security (device, url, apps, wifi) to your app so that you can ensure that your app and data is not compromised when infected device access your apps.
Using SysIntegrity API
To use SysIntegrity API, there are only a few simple steps.
First, add the required app gallery plugin and safety detect dependency to your build.gradle
Project build.gradle:
Code:
dependencies {
implementation 'com.huawei.hms:safetydetect:{version}'
}
Module build.gradle:
Code:
implementation 'com.huawei.hms:safetydetect:5.0.1.300'
Next, to call the API you will need to generate a nonce value, this value must be at least 16 bytes and is only valid once. This value will be returned also within the SafetyDetect response, so you can use this value to check the validity of the Response.
A nonce can be the app username appended with the timestamp of the request, or any unique value of the application.
Code:
fun invokeSysIntegrityCheck() {
val mClient = SafetyDetect.getClient(this)
val nonce = "This Is Just A Test Nonce".toByteArray(Charsets.UTF_8)
val task = mClient.sysIntegrity(nonce, "your app id")
task.addOnSuccessListener {res ->
// sys integrity call success, result is returned
val result = res.result
}.addOnFailureListener {
// something went wrong, please check your app id and if you have enabled the safety detect api in developer console
it.printStackTrace()
}
}
The result of the check is returned in a json format, sample is below:
Code:
{
"advice":"RESTORE_TO_FACTORY_ROM",
"apkCertificateDigestSha256":[
"yT5JtXRgeIgXssx1gQTsMA9GzM9ER4xAgCsCC69Fz3I="
],
"apkDigestSha256":"6Ihk8Wcv1MLm0O5KUCEVYCI/0KWzAHn9DyN38R3WYu8=",
"apkPackageName":"com.huawei.hms.safetydetectsample",
"basicIntegrity":false,
"nonce":"R2Rra24fVm5xa2Mg",
"timestampMs":1571708929141
}
If the basicIntegrity parameter is false, the user device is at risk. You can choose to notify the user or block the access of the app if you think it’s necessary.
Using AppsCheck API
AppsCheck api is a service where it will detect if any malicious apps are installed on the device. The signature of this malicious apps is stored in the cloud and is periodically updated.
Code:
fun invokeAppsCheck() {
val mClient = SafetyDetect.getClient(this)
val task = mClient.maliciousAppsList
task.addOnSuccessListener {
// service call is successful. we will need to check the app lsit response to see if there is any malicious app installed
val appDataList = it.maliciousAppsList
if (it.rtnCode == CommonCode.OK) {
if (appDataList.isEmpty()){
// malicious apps list is empty, device is safe.
}
else {
// malicious apps is detected on the device
appDataList.forEach {
Log.w("warn", "Malicious app detected: ${it.apkCategory}, ${it.apkPackageName}, ${it.apkSha256}")
}
}
}
}.addOnFailureListener {
// something went wrong
}
}
Using URLCheck API
URLCheck is a service which allows the app to check a particular URL for malicious URL. This is very useful for app that re-directs the user to a lot of other 3rd party URL, such as barcode scanner.
To use URL Check Api:
Code:
fun doUrlCheck(url: String) {
val mClient = SafetyDetect.getClient(this)
// Check the URL
// Param1: URL To be checked
// Param2: your Huawei App ID
// Param3&4: Types of threat to be checked
mClient.urlCheck(url, "your app id here",
UrlCheckThreat.MALWARE,
UrlCheckThreat.PHISHING)
.addOnSuccessListener {
if (it.urlCheckResponse.isEmpty()) {
// No threat or phishing detected
} else {
// Threat detected
}
}
}
So there we have it. Safety Detect is really useful to secure your apps from threats when user is using the app.
For more information, please see the developer link:
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/introduction-0000001050156325

Related

Getting Started with Huawei's Safety Detect SDK

Today we're going to go through getting started with Huawei's Safety Detect SDK. We'll talk about how to get set up, as well as some basic examples of how to use it.
What is Huawei 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.
Preparation
First up, make sure you have a Huawei Developer Account. This process can take a couple days, and you'll need one to use this SDK, so be sure to start that as soon as possible. You can sign up at https://developer.huawei.com.
Next, you'll want to obtain the SHA-256 representation of your app's signing key. If you don't have a signing key yet, be sure to create one before continuing. To obtain your signing key's SHA-256, you'll need to use Keytool which is part of the JDK installation. Keytool is a command-line program. If you're on Windows, open CMD. If you're on Linux, open Terminal.
On Windows, you'll need to "cd" into the directory containing the Keytool executable. For example, if you have JDK 1.8 v231 installed, Keytool will be located at the following path:
Code:
C:\Program Files\Java\jdk1.8.0_231\bin\
Once you find the directory, "cd" into it:
Code:
C: #Make sure you're in the right drive
cd C:\Program Files\Java\jdk1.8.0_231\bin\
Next, you need to find the location of your keystore. Using Android's debug keystore as an example, where the Android SDK is hosted on the "E:" drive in Windows, the path will be as follows:
Code:
E:\AndroidSDK\.android\debug.keystore
(Keytool also supports JKS-format keystores.)
Now you're ready to run the command. On Windows, it'll look something like this:
Code:
keytool -list -v -keystore E:\AndroidSDK\.android\debug.keystore
On Linux, the command should be similar, just using UNIX-style paths instead.
Enter the keystore password, and the key name (if applicable), and you'll be presented with something similar to the following:
{
"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"
}
Make note of the SHA256 field.
SDK Setup
Now we're ready to add the Safety Detect SDK to your Android Studio project. Go to your Huawei Developer Console and click the HUAWEI AppGallery tile. Agree to the terms of use if prompted.
Click the "My projects" tile here. If you haven't already added your project to the AppGallery, add it now. You'll be asked for a project name. Make it something descriptive so you know what it's for.
Now, you should be on a screen that looks something like the following:
Click the "Add app" button. Here, you'll need to provide some details about your app, like its name and package name.
Once you click OK, some SDK setup instructions will be displayed. Follow them to get everything added to your project. You'll also need to add the following to the "dependencies" section of your app-level build.gradle file:
Code:
implementation 'com.huawei.hms:safetydetect:4.0.3.300'
If you ever need to come back to these instructions, you can always click the "Add SDK" button after "App information" on the "Project setting" page.
Now you should be back on the "Project setting" page. Find the "SHA-256 certificate fingerprint" field under "App information," click the "+" button, and paste your SHA-256.
Now, go to the Manage APIs tab on the "Project setting" page. Scroll down until you find "Safety Detect" and make sure it's enabled.
Now, if you're using obfuscation in your app, you'll need to whitelist a few things for HMS to work properly.
For ProGuard:
Code:
-ignorewarnings
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-keep class com.hianalytics.android.**{*;}
-keep class com.huawei.updatesdk.**{*;}
-keep class com.huawei.hms.**{*;}
For AndResGuard:
Code:
"R.string.hms*",
"R.string.agc*",
"R.string.connect_server_fail_prompt_toast",
"R.string.getting_message_fail_prompt_toast",
"R.string.no_available_network_prompt_toast",
"R.string.third_app_*",
"R.string.upsdk_*",
"R.layout.hms*",
"R.layout.upsdk_*",
"R.drawable.upsdk*",
"R.color.upsdk*",
"R.dimen.upsdk*",
"R.style.upsdk*
That's it! The Safety Detect SDK should now be available in your project.
Basic Usage
Now that the SDK is set up, it's time to actually use it. Safety Detect currently has 5 APIs you can use: SysIntegrity, AppsCheck, URLCheck, UserDetect, and WifiDetect.
The first thing you'll want to do when using any of these APIs is to check whether HMS is actually available:
Code:
if (HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(context) == ConnectionResult.SUCCESS) {
//HMS is available
} else {
//HMS isn't available. Act accordingly
}
Without HMS, these APIs won't work. Prompt the user to update/install HMS, or simply silently fail, depending on your needs.
SysIntegrity
The SysIntegrity API is similar to Google's SafetyNet. You can use it to check if the current device is insecure (e.g. is rooted). If your app relies heavily on running in a secure environment, you can use this check and refuse to run if it fails.
To use SysIntegrity, you need a few things: a nonce value, your app ID, and an active internet connection. The nonce value should be a secret, needs to be at least 16 bytes long, and can only be used once. It'll be contained in the SysIntegrity check result, so you can verify that the result is genuine.
You can find your app ID under the "App information" section on the "Project Setting" page.
Here's a quick example in Kotlin of how to set up the integrity check request:
Code:
fun checkSysIntegrity() {
val client = SafetyDetect.getClient(context)
//Ideally, you'd use a better nonce generation method, either from your own server
//or some other secure environment.
val nonce = "NONCE_${System.currentTimeMillis()}".toByteArray()
//You can find your APP_ID under "App information" in your project's settings on the AppGallery Developer Console.
val task = client.sysIntegrity(nonce, "APP_ID")
task.addOnSuccessListener {
val result = it.result
//Process the result. It will be in the JWS (JSON Web Signature) format, containing a header, payload, and signature.
//The header contains a certificate chain that should be verified against the HUAWEI CGB Root CA.
//Make sure the domain name of the leaf certificate in the certificate chain is `sysintegrity.platform.hicloud.com`.
//Verify the signature.
//Next, check the payload for the nonce and result. The payload format is something like the following:
/*
{
"advice":"RESTORE_TO_FACTORY_ROM",
"apkCertificateDigestSha256":[
"yT5JtXRgeIgXssx1gQTsMA9GzM9ER4xAgCsCC69Fz3I="
],
"apkDigestSha256":"6Ihk8Wcv1MLm0O5KUCEVYCI/0KWzAHn9DyN38R3WYu8=",
"apkPackageName":"com.huawei.hms.safetydetectsample",
"basicIntegrity":false,
"nonce":"R2Rra24fVm5xa2Mg",
"timestampMs":1571708929141
}
*/
//If basicIntegrity is false, then the check failed and you should handle it appropriately.
//You can use the following library to parse the JWS string: https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt
}
task.addOnFailureListener {
//The request failed. If `it is ApiException`, you might be able to get more details about why the failure occurred.
//Note that this doesn't imply that the device has failed an integrity check. It merely means the check itself has failed.
//You can choose to retry, or simply ignore the error.
}
}
Put that code in a helper Class, and call it as needed. It is an asynchronous API, so make sure you account for that.
AppsCheck
The functionality of AppsCheck is pretty simple. It'll return a list of apps installed on the current device that are potentially malicious. You can use the result to decide on whether or not you want to restrict features, or prevent your app from running.
To get you up and running, a basic example is provided below. You can use it as the base for your implementation.
Code:
fun checkApps() {
val client = SafetyDetect.getClient(context)
val task = client.maliciousAppsList
task.addOnSuccessListener {
if (it.rtnCode == CommonCode.OK) {
//The request truly succeeded
//An ArrayList<MaliciousAppsData>
val apps = it.maliciousAppsList
if (apps.isEmpty()) {
//No malicious apps were found
} else {
//HMS found at least one potentially malicious app
apps.forEach {
//The package name of the potentially malicious app
val packageName = it.apkPackageName
//The SHA-256 of the potentially malicious app
val sha256 = it.apkSha256
//The category of the potentially malicious app
//Constants are defined in the AppsCheckConstants class
//Currently, this can return either VIRUS_LEVEL_RISK (1)
//or VIRUS_LEVEL_VIRUS (2)
val category = it.apkCategory
}
}
} else {
//Something went wrong with the request
//Use `it.getErrorReason()` to see why
}
}
task.addOnFailureListener {
//A communication error occurred.
//If `it is ApiException`, you may be able to get more details about why
//the failure occurred.
}
}
URLCheck
If your app allows users to visit URLs you aren't able to verify (forum app, social media app, etc), URLCheck can help you screen links that users click. If the URL is detected as malicious, you can prompt the user.
If you want to implement this for yourself, start with the example code below:
Code:
fun checkUrl(url: String) {
val client = SafetyDetect.getClient(context)
//Ensure the URLCheck API is initialized
val initTask = client.initUrlCheck()
initTask.addOnSuccessListener {
//Initialization was successful.
//Make sure to remove any query parameters from the URL before checking it.
//For example, if the full URL is https://google.com/home?someKey=true, you need to pass
//https://google.com/home.
//...
//UrlCheckThreat can either be MALWARE or PHISHING.
//Each category is fairly self-explanatory.
//The threat argument is a vararg, so you can pass one or both.
//...
//You can find your APP_ID under "App information" in your project's settings on the AppGallery Developer Console.
val task = client.urlCheck(url, "APP_ID", UrlCheckThreat.MALWARE, UrlCheckThreat.PHISHING)
task.addOnSuccessListener {
//API call succeeded.
if (it.urlCheckResponse.isEmpty()) {
//URL is safe, continue
} else {
//URL matches either MALWARE or PHISHING category or both. Notify user or refuse to visit
//as is appropriate.
//Get the first threat type. Will match one of the UrlCheckThreat values you pass to
//the urlCheck() method.
val type = it.urlCheckResponse[0].urlCheckResult
}
}
task.addOnFailureListener {
//A communication error occurred.
//If `it is ApiException`, you may be able to get more details about why
//the failure occurred.
}
}
initTask.addOnFailureListener {
//Initialization failed.
}
}
UserDetect
UserDetect is essentially a realtime Captcha service. If there are parts of your app that you don't want to be used by bots (i.e. a clicker game), you can trigger a detection request to determine if the user is a bot or a person.
This one is a bit more complicated to implement, as it requires that you use HMS' cloud API to make the final request (i.e. a web server).
An example implementation in Kotlin is shown below:
Code:
fun checkUser() {
val client = SafetyDetect.getClient(context)
val initTask = client.initUserDetect()
initTask.addOnSuccessListener {
//Initialization was successful
//You can find your APP_ID under "App information" in your project's settings on the AppGallery Developer Console.
val task = client.userDetection("APP_ID")
task.addOnSuccessListener {
val responseToken = it.responseToken
if (responseToken.isNotEmpty()) {
//Pass this token to your server, and have it call HMS' cloud API to do the actual verification.
}
}
task.addOnFailureListener {
//A communication error occurred.
//If `it is ApiException`, you may be able to get more details about why
//the failure occurred.
}
}
initTask.addOnFailureListener {
//Initialization failed.
}
}
WifiDetect
If your app relies on a secure WiFi connection, this API may be helpful to you. You can use it to check whether the user's WiFI connection is actually secure.
Here's a quick example stub in Kotlin:
Code:
fun checkWifi() {
val client = SafetyDetect.getClient(context)
val task = client.wifiDetectStatus
task.addOnSuccessListener {
//The status will either be 0, 1, or 2, depending on the WiFi network state.
//0 means WiFi is disconnected.
//1 means WiFi is secure.
//2 means WiFi is insecure.
val status = it.wifiDetectStatus
}
task.addOnFailureListener {
//A communication error occurred.
//If `it is ApiException`, you may be able to get more details about why
//the failure occurred.
}
}
Conclusion
That's all for the SafetyDetect SDK! If you're using HMS, or you're planning to use HMS, and you want to make sure your app is properly secured, the various included APIs are sure to be helpful.
You can find more details, including the full API reference for the Safety Detect SDK, on Huawei's developer website.

Account Kit: Faster app adoption through quick and secure app sign-in using HUAWEI ID

More information like this, you can visit HUAWEI Developer Forum​
Opportunity
The vast number of apps are available for download. There are apps intended for specific purposes and there are similar apps intended for the same purposes. As more apps are available, there is a tendency for users to download more apps to address specific needs or maximize the unique features of similar apps for addressing their needs.
Having an immense number of apps brings stiff competition among developers to ensure that their apps stand out among the other apps. For app users, having a lot of apps in their devices could lead to increased password fatigue and complicated username and password management. It would be a win-win solution for both developers and app end users to have a way that enables app users to sign-in to apps in a fast, simple, and secure manner to have a positive user experience resulting to possible faster app adoption.
What is HUAWEI Account Kit?
HUAWEI Account Kit enables developers to add simple, secure, and quick sign-in and authorization functions to their apps. These functions are achieved by the HUAWEI ID sign-in and authorization solution that complies with OAuth.20 and OpenID Connect. The HUAWEI ID sign-in and authorization solution enables stand-alone apps and apps running on independent developer servers to access user authentication information such as an ID token to enable users to quickly and securely sign-in to apps using their HUAWEI IDs.
How to sign in with HUAWEI ID (ID Token)?
Service process for signing in with HUAWEI ID (ID Token)
The following steps explain the process flow for signing in with HUAWEI ID (ID Token):
1. The app sends a sign-in request to HUAWEI Account SDK upon user sign-in using HUAWEI ID.
2. HUAWEI Account SDK informs the user about the content to be authorized using a user sign-in authorization interface based on the authorization scope in the sign-in request.
3. HUAWEI Account SDK sends an ID token to the app after the user authorizes the app to access the content.
4. The app verifies the ID token.
To request authorization for obtaining an ID token:
1. Present the HUAWEI ID sign-in icon on a sign-in page.
2. Send an authorization request by calling HuaweiIdAuthParamsHelper.setIdToken.
Code:
HuaweiIdAuthParams authParams = new HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM).setIdToken().createParams();
3. Initialize HuaweiIdAuthService using getService method of HuaweiIdAuthManager.
Code:
HuaweiIdAuthService authService = HuaweiIdAuthManager.getService(MainActivity.this, authParams);
4. Bring up the HUAWEI ID sign-in authorization interface using HuaweiIdAuthService.getSignInIntent.
Code:
startActivityForResult(authService.getSignInIntent(), 8888);
5. Obtain HUAWEI ID from the sign-in result by calling HuaweiIdAuthManager.parseAuthResultFromIntent of onActivityResult after completing sign-in authorization.
Code:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
//Process the sign-in and authorization result and obtain an ID token from AuthHuaweiId.
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 8888) {
Task<AuthHuaweiId> authHuaweiIdTask = HuaweiIdAuthManager.parseAuthResultFromIntent(data);
if (authHuaweiIdTask.isSuccessful()) {
//The sign-in is successful, and the user's HUAWEI ID information and ID token are obtained.
AuthHuaweiId huaweiAccount = authHuaweiIdTask.getResult();
Log.i(TAG, "idToken:" + huaweiAccount.getIdToken());
} else {
//The sign-in failed.
Log.e(TAG, "sign in failed : " +((ApiException)authHuaweiIdTask.getException()).getStatusCode());
}
}
}
To verify ID token validity via local verification
Note: Local verification might fail if the current time of the mobile phone is not in sync with the standard time. Make sure that the current time of the mobile phone to be used for local verification is in sync with the standard time.
1. Obtain a public key from the URI obtained from the jwks_uri field at https://oauth-login.cloud.huawei.com/.well-known/openid-configuration.
Code:
private void getJwks(ICallBack iCallBack) {
OkHttpClient okHttpClient = new OkHttpClient();
final Request request = new Request.Builder()
.url(Constant.CERT_URL)
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i(TAG, "Get ID Token failed.");
iCallBack.onFailed();
}
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
try {
String res = response.body().string();
JSONObject jsonObject = new JSONObject(res);
mJsonArray = jsonObject.getJSONArray("keys");
iCallBack.onSuccess();
} catch (NullPointerException | JSONException | IOException e) {
Log.i(TAG, "parse JsonArray failed." + e.getMessage());
iCallBack.onFailed();
}
}
}
});
}
NOTE: The public key is updated daily and is cached in the app server.
2. Verify the ID token using JWT library such as jwt.io.
Code:
DecodedJWT decoder = JWT.decode(idToken);
Algorithm algorithm = Algorithm.RSA256(mRSAPublicKey , null);
JWTVerifier verifier = JWT.require(algorithm).build();
// verify signature
verifier.verify(decoder);
3. Check if the value of iss is https://accounts.huawei.com.
Code:
decoder.getIssuer().equals(ID_TOKEN_ISSUE);
4. Check if the value of aud is the same as the client_id of the app.
Code:
decoder.getAudience().get(0).equals(CLIENT_ID);
5. Check if the expiration of the ID token is based on the value of exp.
Code:
// Expired, throws TokenExpiredException
verifier.verify(decoder);
If the values of the iss and aud checks out and the expiration of the ID Token is based on the value of exp, the ID token is successfully verified, and the app can use the user information in the sign-in result.
Code:
JSONObject jsonObject = new JSONObject(new String(Base64.decode(decoder.getPayload(), Base64.URL_SAFE), DEFAULT_CHARSET));
For the code, you can refer to IDTokenParser.java in the Client Sample Code.
The Benefits
To the developers
Developers can provide a positive user experience to their app users since the app users do not need to undergo a registration and verification process when signing-in to apps using their HUAWEI ID.
To the end user
End users can sign-in to apps in a fast, simple, and secure manner. Minimizes username and password management since end users can sign-in to different apps integrating the Account Kit just by using their HUAWEI ID.
Learn More
To know more information on how to maximize the features and advantages of HUAWEI Account Kit, go to https://developer.huawei.com/consumer/en/hms/huawei-accountkit
Thank you so much for the post. It is very useful
Is there any image or icons specification like facebook login button has?

Beginner: Integration of Huawei Site Kit and Cloud DB in Android (Kotlin)

Introduction
In this article, we will be learning how to integrate the Huawei Site kit and Cloud DB integrations in Android using Kotlin. Using AGC Cloud DB service, Service Providers from multiple cities can manage their data through CRUD (Create, Read, Update and Delete) operations. Using Site Kit user can search nearby stores and check stores details.
Development Overview
You need to install Android Studio and I assume that you have prior knowledge about the Android and Kotlin
Hardware Requirements
A computer (desktop or laptop) running Windows 10.
A Huawei phone (with the USB cable), which is used for debugging.
Software Requirements
Java JDK 1.7 or later
Android studio installed.
HMS Core (APK) 4.x or later
Integration of Site Kit
Site Kit provides place search services including keyword search, nearby place search, place detail search, and place search suggestion, helping your app provide convenient place-related services to attract more users and improve user loyalty.
Step 1: To integrate Site kit, need to add the below library:
Java:
implementation 'com.huawei.hms:site:5.0.2.300’
Step 2: How to get API Key?
Create an app in AppGallery Connect enter all necessary information.
Generate and configure the signing certificate fingerprint.
Add the AppGallery Connect plug-in and the Maven repository in the project-level build.gradle file. Configure the signature file in Android Studio.
For details, refer to Preparations for Integrating HUAWEI HMS Core.
After configure project, you can find API key in below image.
{
"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"
}
Step 3: To initialize HMS Site kit:
Java:
searchService = SearchServiceFactory.create(this, Utils.getApiKey())
To fetch nearby stores based on user’s current location:
SearchServiceActivity.kt
Java:
intent.let {
val request = NearbySearchRequest().apply {
queryString=it.getStringExtra(AppConstants.REQUEST_QUERY).toString()
setQuery(queryString)
setLocation(
Coordinate(
it.getDoubleExtra(AppConstants.SERVICE_LAT_KEY, 0.0),
it.getDoubleExtra(AppConstants.SERVICE_LNG_KEY, 0.0)
)
)
}
imageString = it.getStringExtra(AppConstants.PROVIDER_IMAGE_KEY).toString()
searchService?.nearbySearch(request, searchResultListener)
}
Results
Note: For more details, refer Site kit integration procedure. Site kit- SDK integration
Integration of Cloud DB
In this scenario, we can learn about a database service that runs on a cloud and is accessible from anywhere. Find the following points for better understand:
How to use Cloud DB to develop applications?
How to read, write and query application data to Cloud DB?
This service provides the synergy database product that provides data synergy management capabilities between the device and the cloud, unified data models, and various data management APIs.
Cloud DB supplies us simply cloud database and our project work together successfully and we can take advantages of CRUD (Create, Read, Update and Delete) operations.
Prerequisites
To use the Cloud DB for build application service, you need to complete the following preparations:
You have registered an account on the AppGallery Connect console and passed real-name authentication.
You have created a project and application on the AppGallery Connect console.
You have enabled the Anonymous account authentication service for the application to use permissions of the authentication user.
You have installed Android Studio on the local host.
Enable Cloud DB Service
Before using the Cloud DB service, you need to enable it.
Log in to AppGallery Connect and click My Projects.
Select a project from the project list and click an app for which you need to enable the Cloud DB service.
In the navigation bar, choose Build > Cloud DB.
Click Enable now to enable the Cloud DB service.
Select the Data storage location.
After the service is initialized, the Cloud DB service is enabled successfully.
Adding and Exporting Object Types
The following example shows how to create object types on the AppGallery Connect console and export the object type file in the Java format for Android application development.
1. Log in to AppGallery Connect and click My projects.
2. Select a project from the project list and click an app for which you need to add an object type.
3. In the navigation bar, choose Build > Cloud DB.
4. Click Add to navigate to the object type creation page.
5. Set Object Type Name to LoginInfo, and click Next.
6. Click “+Add Field”, add the fields as per your requirements and click Next.
7. (Optional) Click “+Add Index”.
8. Add permissions as follows and click Next. (For Everyone, upsert and delete access is not allowed).
9. Click OK. Object types are created and displayed in the object type list.
The created object types are displayed in the object type list.
Repeat the above steps to create to create multiple table.
10. Click “Export” button
11. Set the format of the file to be exported to JAVA.
12. Set the Java file type to Android.
13. Enter the package name in the JAVA file.
The package name can contain only the following three types:\
Letters: A–Z or a–z, which are mandatory
Digits: 0–9
Special characters: underscore (_) and period (.)
15. Click Export. The file that contains all object types will be downloaded. Add the exported JAVA file in your project. The file that contains all object types in the version is exported to the local PC. The exported Java file will be added to the local development environment in subsequent steps.
Cloud DB Zone
You can create a Cloud DB zone on the AppGallery Connect console. Perform the following steps to set Cloud DB Zone Name.
1. Log in to AppGallery Connect and click My projects.
2. Select a project from the project list and click an app for which you need to add a Cloud DB zone.
3. In the navigation tree, choose Build > Cloud DB.
4. Click the Cloud DB Zones tab.
5. Click Add to go to the Cloud DB zone creation page.
6. Enter Your App Name in the Cloud DB Zone Name text box.
7. Click OK the created Cloud DB zones are displayed in the Cloud DB zone list.
Configuring the Development Environment
Add a Cloud DB SDK to the dependencies node in the build.gradle file in the Project/app directory.
Java:
implementation <strong>'com.huawei.agconnect:agconnect=database:1.2.3.301’</strong>
In the build.gradle file, set the compatibility mode of Java source code to JDK1.8.
Java:
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
Adding Object Type Files
During application development, you can directly add the JAVA files exported from the AppGallery Connect console to the local development environment, and use the createObjectType() method in the AGConnectCloudDB class to define and create object types. Then you do not need to create object types for local application development.
Add all exported files to the local development environment.
Initialize Cloud DB. Use the createObjectType() method in the AGConnectCloudDB class to define and create object types.
Initializing
After adding an object type file, you can use the Cloud DB to develop an application. When developing an application, you need to initialize AGConnectCloudDB, and create Cloud DB zone and object types.
Initialize AGConnectCloudDB in an application’s CloudDBZoneWrapper
Java:
public static void initAGConnectCloudDB(Context context) {
AGConnectCloudDB.initialize(context);
}
Obtain the AGConnectCloudDB instance and create object types.
Java:
mCloudDB = AGConnectCloudDB.getInstance();
mCloudDB.createObjectType(ObjectTypeInfoHelper.getObjectTypeInfo());
Create the Cloud DB zone configuration object and open the Cloud DB zone. Add the below code in CloudDBZoneWrapper class.
Java:
public void openCloudDBZoneV2() {
mConfig = new CloudDBZoneConfig(AppConstants.URBAN_HOME_SERVICES,
CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE, CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
mConfig.setPersistenceEnabled(true);
Task<CloudDBZone> openDBZoneTask = mCloudDB.openCloudDBZone2(mConfig, true);
openDBZoneTask.addOnSuccessListener(cloudDBZone -> {
Log.w(TAG, "open clouddbzone success");
mCloudDBZone = cloudDBZone;
// Add subscription after opening cloudDBZone success
mUiCallBack.onInitCloud();
addSubscription();
}).addOnFailureListener(e ->
Log.w(TAG, "open clouddbzone failed for”));
}
Writing Data
You can use the executeUpsert() API to write one object or a group of objects to the current Cloud DB zone.
Add the below code in CloudDBZoneWrapper class.
Java:
public void insertDbZoneInfo(T objectInfo) {
if (mCloudDBZone == null) {
Log.w(TAG, "CloudDBZone is null, try re-open it");
return;
}
Task<Integer> upsertTask = mCloudDBZone.executeUpsert(objectInfo);
upsertTask.addOnSuccessListener(cloudDBZoneResult -> {
mUiCallBack.onInsertSuccess(cloudDBZoneResult);
}).addOnFailureListener(e -> {
mUiCallBack.updateUiOnError("Insert table info failed");
});
}
Viewing Data
Data added on the application page will be stored on the cloud. After a listener for data changes is registered on the device, the device will be notified when there is any changes on the cloud, and the local data will be updated in time.
You can use the query condition together with the subscribeSnapshot() method to specify an object to be listened on. When the data of the object is changed, the device will be notified and the original data stored on the cloud will be synchronized to the device based on the data changes information obtained by snapshots.
Add the below code in CloudDBZoneWrapper class.
Java:
private OnSnapshotListener<T> mSnapshotListener = (cloudDBZoneSnapshot, e) -> {
if (e != null) {
Log.w(TAG, "onSnapshot" );
return;
}
CloudDBZoneObjectList<T> snapshotObjects = cloudDBZoneSnapshot.getSnapshotObjects();
List<T> dbZoneList = new ArrayList<>();
try {
if (snapshotObjects != null) {
while (snapshotObjects.hasNext()) {
T objectInfo = snapshotObjects.next();
dbZoneList.add(objectInfo);
}
}
mUiCallBack.onSubscribe(dbZoneList);
} catch (AGConnectCloudDBException snapshotException) {
Log.w(TAG, "onSnapshot:(getObject)");
} finally {
cloudDBZoneSnapshot.release();
}
};
Querying Data
The executeQuery(), addOnSuccessListener(), and addOnFailureListener() methods are used together to query data in asynchronous mode.
Add the below code in CloudDBZoneWrapper class.
Java:
public void queryAllData(CloudDBZoneQuery<T> query) {
if (mCloudDBZone == null) {
Log.w(TAG, "CloudDBZone is null, try re-open it");
return;
}
Task<CloudDBZoneSnapshot<T>> queryTask = mCloudDBZone.executeQuery(query,
CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
queryTask.addOnSuccessListener(new OnSuccessListener<CloudDBZoneSnapshot<T>>() {
@Override
public void onSuccess(CloudDBZoneSnapshot<T> snapshot) {
processQueryResult(snapshot);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
mUiCallBack.updateUiOnError("Query failed");
}
});
}
Deleting Data
You can use the executeDelete() method to delete a single object or a group of objects. When deleting data, Cloud DB will delete the corresponding data based on the input object primary key and does not check whether other attributes of the object are consistent with the stored data. When a group of objects are deleted, the deletion operation is atomic. That is, objects in the list are either all deleted successfully or all fail to be deleted.
Add the below code in CloudDBZoneWrapper class.
Java:
public void deleteTableData(List<T> tableObject) {
if (mCloudDBZone == null) {
Log.w(TAG, "CloudDBZone is null, try re-open it");
return;
}
Task<Integer> deleteTask = mCloudDBZone.executeDelete(tableObject);
if (deleteTask.getException() != null) {
mUiCallBack.updateUiOnError("Delete service type table failed");
return;
}
mUiCallBack.onDelete(tableObject);
}
}
Editing Data
You can use the editService() method to edit professional’s details.
Add the below code in ManageServiceActivity.kt class.
Java:
override fun editService(listObject: ServiceType) {
val intent = Intent(this, AddServiceActivity::class.java)
intent.apply {
putExtra(AppConstants.CATEGORY_NAME, listObject.cat_name)
putExtra(AppConstants.PROVIDER_PH_NUM, listObject.phone_number.toString())
putExtra(AppConstants.PROVIDER_MAIL_ID, listObject.email_id)
putExtra(AppConstants.PROVIDER_COUNTRY, listObject.country)
putExtra(AppConstants.PROVIDER_ID, listObject.id)
putExtra(AppConstants.PROVIDER_NAME, listObject.service_provider_name)
putExtra(AppConstants.PROVIDER_CITY, listObject.city)
putExtra(AppConstants.PROVIDER_STATE, listObject.state)
}
startActivity(intent)
}
Results
Tips and Tricks
Always use the latest version of the library.
Always provide proper Roles to user in Object Types.
Note: For more details, please refer Cloud Db service integration documentation, Cloud DB- Introduction
Conclusion
By using Cloud DB you can connect with Database without the need of APIs. You can perform the CRUD operations and also can restrict user access for the tables. Easy to connect with the application. Also you can search nearby service (plumber, electrician, painter etc) using site kit like as etc.
Site Kit to fetch nearby stores.
Cloud DB service to add, delete and edit professional’s details.
References
Site Kit
Cloud DB- Applying for Cloud BD
Cloud DB- Introduction
Read In Forum
Does CloudDB supports offline operations ?
What is the max size of data to be stored on Cloud DB?

Writing a Serverless Android app (ft. Huawei's AppGallery Connect)

Part 0 - Why?
Part 1 - Auth
Part 2 - CloudDB
Part 3 - More Cloud
Part 4 - Login and Register
Over the next couple of months I will be releasing a complete end to end guide to creating an Android app using serverless functionality to completely remove the need for any backend server/hosting etc.
These guides will be made up of a weekly live stream which will then be edited into a YouTube video along with each having a blog post for those that prefer a written guide!
But before we get into the actual guide (which will start with 'Part 1' next week) lets start from the... well start! Why might someone want to build a serverless app? what IS a serverless app? and what does Huawei's AppGallery Connect have to do with it?
Well I'm glad you asked!
What is a serverless app?​If Cloud computing takes away the need to manage the hardware, serverless computing takes away the need to manage the software. Its an extension of cloud computing where the provider handles everything about the servers and simply provides some kind of interface for the user to access their services. This might be in the form of an API, SDK, GUI or all of the above!
Why would I want to build a serverless app?​Server management is in its own right a full time job, from setting up the environment to installing and managing the software stack. Security updates, security hardening, authentication (to name a few) are all things that need to be managed in a traditional backend server setup. By using a serverless service all of this management work is removed, you as the app developer simply have access to the resources you need when you need them.
A couple of examples of why or when you might use a serverless system:
Example one - Prototyping​If you need to build an application prototype quickly, you want something that is just going to work and don't want the hassle of setting everything up! By using a serverless system you have instant access to the services you need, this lets you focus on prototyping the app itself.
Example two - Basic requirements​Many apps have very basic backend server requirements. Perhaps they just want to store users details and setting preferences. Or maybe they just need a way to host and download files. These kinds of requirements tend to mean that a full managed backend server is overkill. When your requirements are simple no one wants to spend hours setting it up and managing a server!
Example Three - Small Team/Limited knowledge​If your a small team (or solo) you might simply not have the knowledge or man power to manage servers. The time taken to learn and maintain that knowledge might significantly impact the amount of time you have to develop your application. Sometimes its just much more cost effective to let another company manage this.
What does Huawei's AppGallery Connect have to do with it?​As part of Huawei's AppGallery platform they now offer a wide range of serverless features and functionality. These services come under the AppGallery Connect suite, including but not limited to, database, web hosting, authentication and storage. These services include generous free tiers which make prototyping and developing using these services even more attractive and cost effective.
Because of this we will be using this platform throughout the development guides as we explore what can be done with a serverless Android app!
Part 1 - Auth​
This weeks Video is below
But for those that would rather a written guide, lets get into it!
Project Setup​Starting with a brand new project (or one that has never used Huawei services) we will need to start by setting up a new app. (If you haven't setup your developer account yet sign up!)
If you would like the complete step by step guide on how to get setup check out the Official Documentation, but below is a summery.
Navigate to the the AppGallery Connect area of the developer portal, here you need to create a new project.
{
"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"
}
Follow the new project guide, giving that project a name. Once completed on the left hand menu find the Auth service under the build menu.
Click the enable now button in the top right corner to enable this service for your project. Set a default data processing location, depending on your physical location will most likely help decide which to use. For us we have selected Germany as this is the closest location and within the EU.
From here you are presented with the list of authentication modes, Huawei supports a wide range of services from Facebook login to AppleID. However today we are focusing on the email auth method. Click the enable button next to this.
Now that everything is setup in the project, its time to setup the app! At this point I will assume you have created a new blank project in Android Studio and given it a package name etc.
From the project settings top screen, select the Add app button to setup a new app under this project.
Fill in the add app form, setting the platform, app name, package etc so that it can be added to the created project. Its worth noting at this point, that while we are focusing on Android today many of these services can be used across multiple platforms including iOS and web.
Once completed you will be told to download the agconnect-services.json file and presented with a code to get the core services setup. As we are already focusing on a specific service for today the setup at this point is a little different.
Start by placing your nearly downloaded agconnect-services.json file into the app directory of your project.
Next we will get gradle configured correctly. In your top level gradle build file add the below to your repositories both under buildscript and allprojects
Code:
maven {
url 'https://developer.huawei.com/repo/'
}
And to your dependencies add classpath 'com.huawei.agconnect:agcp:1.4.1.300'
Your file should now look a little like
Code:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
maven {
url 'https://developer.huawei.com/repo/'
}
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.2"
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
maven {
url 'https://developer.huawei.com/repo/'
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Next open your app build.gradle file
Start by adding the agconnect plugin into your plugins list
Code:
plugins {
id 'com.android.application'
id 'com.huawei.agconnect'
}
Make sure to set your midSdkVersion to at least `17` (this is required for any Huawei AppGallery Connect services.
And then in dependencies we are going to add the core and auth services
Code:
implementation 'com.huawei.agconnect:agconnect-core:1.4.1.300'
implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'
So your file should now look something like
Code:
plugins {
id 'com.android.application'
id 'com.huawei.agconnect'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "site.zpweb.barker"
minSdkVersion 17
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 'com.huawei.agconnect:agconnect-core:1.4.1.300'
implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
And thats it! let gradle sync up and we are all good to go!
Registration​As I mentioned before there are a wide range of ways a user can authenticate using the Auth service, but here we are looking at the email service. Email authenticate the user by sending them a code via (your guessed it) Email. The user then inputs this code back into the app to confirm that they are who the say they are, and that they have access to that contact method. We will assume you have setup some kind of register view that will capture a users email.
We start by requesting an authentication code be set to the user, the code to do this looks like
Java:
VerifyCodeSettings settings = VerifyCodeSettings.newBuilder()
.action(VerifyCodeSettings.ACTION_REGISTER_LOGIN)
.sendInterval(30)
.locale(Locale.ENGLISH)
.build();
Task<VerifyCodeResult> task = EmailAuthProvider.requestVerifyCode(emailString, settings);
task.addOnSuccessListener(TaskExecutors.uiThread(), new OnSuccessListener<VerifyCodeResult>() {
@Override
public void onSuccess(VerifyCodeResult verifyCodeResult) {
authCodeDialog();
}
}).addOnFailureListener(TaskExecutors.uiThread(), new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Toast.makeText(RegisterActivity.this,
"Error, code sending failed: " + e,
Toast.LENGTH_LONG).show();
}
});
Lets break this down, we start by defining a `VerifyCodeSettings` object, this holds all the settings relating to the sending of the code. Here we define what locale should be used for the message text that is sent, what kind of code it is and the send interval.
Next we create a task to be run
Java:
Task<VerifyCodeResult> task = EmailAuthProvider.requestVerifyCode(emailString, settings);
Using the EmailAuthProvider, where `emailString` is the email address the user has entered, as a string and the settings object is the VerifyCodeSettings object we just created.
Next we setup an OnSucessListener which will be called if the code was successfully sent to the user. In this example we are calling the method `authCodeDialog();` to display a dialog to enter the code, which will see in a moment.
We also setup an OnFailureListener which simply create a toast on screen to display what ever error is sent back.
Now that we have sent the user a code we should display some view for them to enter that code, in my instance I have the method below, but of course this could be what ever view you wanted.
Java:
private void authCodeDialog() {
AlertDialog.Builder alert = new AlertDialog.Builder(this);
final EditText authCodeField = new EditText(this);
alert.setMessage("Enter your auth code below");
alert.setTitle("Authentication Code");
alert.setView(authCodeField);
alert.setPositiveButton("Register", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String authCode = authCodeField.getText().toString();
register(authCode);
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(RegisterActivity.this,
"Registration Cancelled",
Toast.LENGTH_LONG).show();
}
});
alert.show();
}
Finally once the user has inputted the code we can register them as you can see above we have a register method that is called, this has the below code:
Java:
EmailUser emailUser = new EmailUser.Builder()
.setEmail(emailString)
.setVerifyCode(authCode)
.build();
AGConnectAuth.getInstance().createUser(emailUser).addOnSuccessListener(new OnSuccessListener<SignInResult>() {
@Override
public void onSuccess(SignInResult signInResult) {
Toast.makeText(RegisterActivity.this,
"Register Successful: " + signInResult.getUser().getUid(),
Toast.LENGTH_LONG);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Toast.makeText(RegisterActivity.this,
"Registering failed " + e,
Toast.LENGTH_LONG);
}
});
So we start by creating an `EmailUser` using the email address we captured earlier and the authCode that the user has entered. Using that `EmailUser` we then attempt to register. Note that we can also set a password against the `EmailUser`, if we do this when they go to login they do not need to verify their email address again. They can just enter their email and password.
As you can see we pass the created object into the createUser method, attaching OnSuccess and OnFailure Listeners. If the user is successfully registered, i.e the code they entered matches what was sent we are returned a `SignInResult` this object containes the user. So in this example we simply print out the registered users UID to confirm it completed successfully.
Login​
Now that we have covered the sign up process lets look at how we might handle a login process. This would assume that the user has already registered via the above method and is now logging into the app, perhaps after installing it on a new phone.
As I mentioned before if the user signed up without a password then start by sending a verify code, in just the same way as we did during the sign up process. Once we have the code we can generate a `AGCOnnectAuthCredential` object
Java:
AGConnectAuthCredential credential = credential = EmailAuthProvider.credentialWithVerifyCode(
email.getText().toString().trim(),
null,
authCode);
Here we get the email that the user entered (`email` being an `EditText` object). We pass null for the password as we haven't used this, and finally the authCode which the user has entered into the app.
Now we can attempt the sign the user in:
Java:
AGConnectAuth.getInstance().signIn(credential)
.addOnSuccessListener(new OnSuccessListener<SignInResult>() {
@Override
public void onSuccess(SignInResult signInResult) {
Toast.makeText(MainActivity.this, "Sign in successful: " +
signInResult.getUser().getUid(), Toast.LENGTH_LONG);
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Toast.makeText(MainActivity.this, "sign in failed:" + e, Toast.LENGTH_LONG);
}
});
We pass the credential object into the signIn method, if the code and email matched and was correct the OnSuccessListener will return the SignInResult object just as it did during the sign up, from here we have access to the users detials.
If for any reason the login fails we will get an Exception in the OnFailureListener.
And thats it! We have setup the application to use Huawei services and configured the app to use email authentication during sign up and log in.
For more information on the Auth service, full documentation can be found here https://developer.huawei.com/consum...Guides/agc-auth-introduction-0000001053732605
We will be back with the next part next week!
Thanks for sharing!!
Except login feature, how will we store huge amount of data?
ask011 said:
Except login feature, how will we store huge amount of data?
Click to expand...
Click to collapse
CloudDB and Cloud Storage are another two services offered which will allow you to store any data you need to! We will be looking at this in the coming weeks so stay tuned!
Part 2 - CloudDB​
This weeks video
Starting with the project as we completed last week (on GitHub) lets now configure the application to support and use the CloudDB functionality from Huawei. Today we will be setting up everything we need to be able to use the CloudDB service and set/get/delete data.
Navigate to the the AppGallery Connect area of the developer portal, select the project we setup last week and on the left hand menu find CloudDB under the build sub menu.
From here enable the service and if you haven't already you will be asked to setup a data location.
Next under the ObjectTypes tab lets create the first data object, click add and you will be presented with a screen like this:
For todays example set your object type name to User, then we will create three fields, id, uid and username as below
Finally create an index called user_id with index field set to id. Leave data permission as they are and save your new data object.
Next go over to the next tab "Cloud DB Zones" and create a new zone, for this example we will call it "Barker".
Head back over to the ObjectTypes tab and press the "Export" button, Pick the JAVA file format, and android for file type. Then enter your Android package name.
This will download two files in a zipped folder, unzip and add these java files to your Android project.
Lets now take a look at these files, if you have followed my naming schemes your User.java should look like:
Java:
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2019-2020. All rights reserved.
* Generated by the CloudDB ObjectType compiler. DO NOT EDIT!
*/
package site.zpweb.barker.model;
import com.huawei.agconnect.cloud.database.CloudDBZoneObject;
import com.huawei.agconnect.cloud.database.Text;
import com.huawei.agconnect.cloud.database.annotations.DefaultValue;
import com.huawei.agconnect.cloud.database.annotations.EntireEncrypted;
import com.huawei.agconnect.cloud.database.annotations.NotNull;
import com.huawei.agconnect.cloud.database.annotations.Indexes;
import com.huawei.agconnect.cloud.database.annotations.PrimaryKeys;
import java.util.Date;
/**
* Definition of ObjectType User.
*
* @since 2021-07-09
*/
@PrimaryKeys({"id"})
@Indexes({"user_id:id"})
public final class User extends CloudDBZoneObject {
private Integer id;
@DefaultValue(stringValue = "0")
private String uid;
@DefaultValue(stringValue = "0")
private String username;
public User() {
super(User.class);
this.uid = "0";
this.username = "0";
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getUid() {
return uid;
}
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
Which as you can see is a fairly standard object class with setup for all the fields we defined in the ObjectType.
The other generated file ObjectTypeInfoHelper.java should look like
Java:
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2019-2020. All rights reserved.
* Generated by the CloudDB ObjectType compiler. DO NOT EDIT!
*/
package site.zpweb.barker.model;
import com.huawei.agconnect.cloud.database.CloudDBZoneObject;
import com.huawei.agconnect.cloud.database.ObjectTypeInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Definition of ObjectType Helper.
*
* @since 2021-07-09
*/
public final class ObjectTypeInfoHelper {
private static final int FORMAT_VERSION = 2;
private static final int OBJECT_TYPE_VERSION = 10;
public static ObjectTypeInfo getObjectTypeInfo() {
ObjectTypeInfo objectTypeInfo = new ObjectTypeInfo();
objectTypeInfo.setFormatVersion(FORMAT_VERSION);
objectTypeInfo.setObjectTypeVersion(OBJECT_TYPE_VERSION);
List<Class<? extends CloudDBZoneObject>> objectTypeList = new ArrayList<>();
Collections.addAll(objectTypeList, User.class);
objectTypeInfo.setObjectTypes(objectTypeList);
return objectTypeInfo;
}
}
Which is a helper class used by the framework to know what Object classes are available, in this instance just the User class.
You will notice that at this point the code doesn't compile! We need to add in the new CloudDB dependency to the apps build.gradle file
Code:
implementation 'com.huawei.agconnect:agconnect-cloud-database:1.4.8.300'
So your gradle file should now look like:
Code:
plugins {
id 'com.android.application'
id 'com.huawei.agconnect'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "site.zpweb.barker"
minSdkVersion 17
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 'com.huawei.agconnect:agconnect-core:1.4.1.300'
implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'
implementation 'com.huawei.agconnect:agconnect-cloud-database:1.4.8.300'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
Once gradle has synced up we are good to go!
To more easily manage the connection between the CloudDB and functionality within the app I suggest written a separate class to do this. Below is my CloudDBManager class which will act as a wrapper handling much of the CloudDB functionality.
Java:
package site.zpweb.barker.db;
import android.content.Context;
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.CloudDBZoneObjectList;
import com.huawei.agconnect.cloud.database.CloudDBZoneQuery;
import com.huawei.agconnect.cloud.database.CloudDBZoneSnapshot;
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 java.util.ArrayList;
import java.util.List;
import site.zpweb.barker.model.User;
import site.zpweb.barker.utils.Toaster;
public class CloudDBManager {
private int maxUserID = 0;
Toaster toaster = new Toaster();
private final AGConnectCloudDB cloudDB;
private CloudDBZone cloudDBZone;
public CloudDBManager(){
cloudDB = AGConnectCloudDB.getInstance();
}
public static void initCloudDB(Context context){
AGConnectCloudDB.initialize(context);
}
public void openCloudDBZone(Context context){
CloudDBZoneConfig config = new CloudDBZoneConfig("Barker",
CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
config.setPersistenceEnabled(true);
try {
cloudDBZone = cloudDB.openCloudDBZone(config, true);
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
}
}
public void closeCloudDBZone(Context context){
try {
cloudDB.closeCloudDBZone(cloudDBZone);
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
}
}
public void upsertUser(User user, Context context) {
Task<Integer> upsertTask = cloudDBZone.executeUpsert(user);
executeTask(upsertTask, context);
}
public void upsertUsers(List<User> users,Context context) {
Task<Integer> upsertTask = cloudDBZone.executeUpsert(users);
executeTask(upsertTask, context);
}
private void executeTask(Task<Integer> task,Context context) {
task.addOnSuccessListener(integer -> toaster.sendSuccessToast(context, "upsert successful"))
.addOnFailureListener(e -> toaster.sendErrorToast(context, e.getLocalizedMessage()));
}
public void deleteUser(User user){
cloudDBZone.executeDelete(user);
}
public int getMaxUserID(){
return maxUserID;
}
private void updateMaxUserID(User user){
if (maxUserID < user.getId()) {
maxUserID = user.getId();
}
}
public void getAllUsers(Context context){
queryUsers(CloudDBZoneQuery.where(User.class), context);
}
public void queryUsers(CloudDBZoneQuery<User> query, Context context) {
Task<CloudDBZoneSnapshot<User>> task = cloudDBZone.executeQuery(query,
CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
task.addOnSuccessListener(userCloudDBZoneSnapshot -> processResults(userCloudDBZoneSnapshot, context))
.addOnFailureListener(e -> toaster.sendErrorToast(context, e.getLocalizedMessage()));
}
private void processResults(CloudDBZoneSnapshot<User> userCloudDBZoneSnapshot, Context context) {
CloudDBZoneObjectList<User> userCursor = userCloudDBZoneSnapshot.getSnapshotObjects();
List<User> userList = new ArrayList<>();
try {
while (userCursor.hasNext()) {
User user = userCursor.next();
updateMaxUserID(user);
userList.add(user);
}
//HAVE USER LIST
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
} finally {
userCloudDBZoneSnapshot.release();
}
}
}
Lets break it down and take a look at what we are going to be able to do with this class.
First we define the variables we are going to need, the most important thing here is maxUserID. CloudDB currently doesn't have any auto increment support so we will need to keep a running check on what is the last used ID.
Next we have the class constructor where we will get an instance of the CloudDB interface to be used by the other methods in this class.
Java:
public CloudDBManager(){
cloudDB = AGConnectCloudDB.getInstance();
}
Next up with a static init method, the CloudDB initialize method must be called at the start of your application, so this static method is used to do just that!
Java:
public static void initCloudDB(Context context){
AGConnectCloudDB.initialize(context);
}
In the openCloudDBZone method we setup the configured cloud zone, this is where the data will be saved to and received from. Note that you could have multiple zone's that all use the same ObjectType's. They wouldn't however have access to other zone's data. Useful if you have multiple applications that require similar data structures.
Java:
public void openCloudDBZone(Context context){
config = new CloudDBZoneConfig("Barker",
CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
config.setPersistenceEnabled(true);
try {
cloudDBZone = cloudDB.openCloudDBZone(config, true);
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
}
}
As we have an open method we should also have a close method to shut down the apps access to that zone.
Java:
public void closeCloudDBZone(Context context){
try {
cloudDB.closeCloudDBZone(cloudDBZone);
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
}
}
Next we have three methods that handle the upsert of User's, that is either the update or insert depending on if the user already exists in the database. The executeTask method sets the success/failure listeners while the other two methods simply set up the task depending on if we are upserting one user or a list of users.
Java:
public void upsertUser(User user, Context context) {
Task<Integer> upsertTask = cloudDBZone.executeUpsert(user);
executeTask(upsertTask, context);
}
public void upsertUsers(List<User> users,Context context) {
Task<Integer> upsertTask = cloudDBZone.executeUpsert(users);
executeTask(upsertTask, context);
}
private void executeTask(Task<Integer> task,Context context) {
task.addOnSuccessListener(integer -> toaster.sendSuccessToast(context, "upsert successful"))
.addOnFailureListener(e -> toaster.sendErrorToast(context, e.getLocalizedMessage()));
}
Next we have a simple method that will delete a User from the database
Java:
public void deleteUser(User user){
cloudDBZone.executeDelete(user);
}
Then a getter for the maxUserID, and a method to update the maxUserID. If the given User has a greater ID than the current max, update the max ID to that Users ID.
Java:
public int getMaxUserID(){
return maxUserID;
}
private void updateMaxUserID(User user){
if (maxUserID < user.getId()) {
maxUserID = user.getId();
}
}
And finally we have three methods that handle the querying of data, the getAllUsers method makes use of a predefined query which simply asks for all objects that are of the type User.
Java:
public void getAllUsers(Context context){
queryUsers(CloudDBZoneQuery.where(User.class), context);
}
Next the queryUsers method which will generate the query task and runs it, on success we then pass the result into processResult.
Java:
public void queryUsers(CloudDBZoneQuery<User> query, Context context) {
Task<CloudDBZoneSnapshot<User>> task = cloudDBZone.executeQuery(query,
CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
task.addOnSuccessListener(userCloudDBZoneSnapshot -> processResults(userCloudDBZoneSnapshot, context))
.addOnFailureListener(e -> toaster.sendErrorToast(context, e.getLocalizedMessage()));
}
Note that for the query variable we can create the type of query we need. We will look at examples of this next week (other than just the provided CloudDBZoneQuery.where(User.class)
The final method in this manager is the processResult, here we use a cursor to move over the result returned from the query. For each object in the result we update the max UserID and then add that User to a list. This is the point where we would then do something with that list, perhaps update the UI to show the result or do some other processing.
Java:
private void processResults(CloudDBZoneSnapshot<User> userCloudDBZoneSnapshot, Context context) {
CloudDBZoneObjectList<User> userCursor = userCloudDBZoneSnapshot.getSnapshotObjects();
List<User> userList = new ArrayList<>();
try {
while (userCursor.hasNext()) {
User user = userCursor.next();
updateMaxUserID(user);
userList.add(user);
}
//HAVE USER LIST
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
} finally {
userCloudDBZoneSnapshot.release();
}
}
We now have all the basic methods we might need to get/set/delete the User object.
We have just a little more setup to do and then we are ready to start using the CloudDB!
As I mentioned earlier we need to init the CloudDB before we can use it anywhere in the app. The best way to do this will be to make it part of the Application class's onCreate method. For example:
Java:
package site.zpweb.barker;
import android.app.Application;
import site.zpweb.barker.db.CloudDBManager;
public class BarkerApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CloudDBManager.initCloudDB(this);
}
}
Setting this as the application class in your manifest:
XML:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="site.zpweb.barker">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Barker"
android:name=".BarkerApplication">
<activity android:name=".RegisterActivity"></activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
And that's it! We are now good to go, next week we will look at how we might actually make use of this functionality as well as expand on ObjectType's and storing more complex data in the cloud!
Can we store custom object cloud db?
Part 3 - More CloudDB​
Last time we looked at the basic setup of the CloudDB service, how to get things configured and how you can get started with the service. Now this is in place lets take a look at the service in more detail. We started working on a CloudDBManager that was going to handle all the communication between the CloudDB service and the rest of the app. This was a great start but before we dig any deep lets look at a few improvements to this.
Listen for Database Changes​
In the current CloudDBManager if we want to check for any changes to the database we had to manually call getAllUsers(). This is fine if we aren't really interested in when changes are made to the database, however if we do want to keep an up to date local copy of the data (for example posts in a feed) we need to look at adding a Snapshot Listener. This will tell the CloudDB service to execute a given query and process the results in real time.
Lets start by defining a new OnSnapShotListener for the Class User when the listener is given a snapshot we pass this into the processResults method we created last week.
Java:
private final OnSnapshotListener<User> snapshotListener = (cloudDBZoneSnapshot, e) -> processResults(cloudDBZoneSnapshot);
Next we create a method that will subscribe that Snapshot Listener to the CloudDBZone with an applied query, in this instance just the simple query to return all Users
Java:
public void addSubscription() {
CloudDBZoneQuery < User > snapshotQuery = CloudDBZoneQuery.where(User.class).equalTo("uid", "");
try {
ListenerHandler handler = cloudDBZone.subscribeSnapshot(snapshotQuery,
CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY,
snapshotListener);
} catch (Exception e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
}
}
With this in place we can now tweak the openCloudDBZone method, by using a task based approach we are able to add an onSuccessListener. So long as the zone is successfully opened we can called the addSubscription() method and start listening for new data.
Java:
public void openCloudDBZoneV2() {
CloudDBZoneConfig config = new CloudDBZoneConfig("Barker",
CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
config.setPersistenceEnabled(true);
Task < CloudDBZone > task = cloudDB.openCloudDBZone2(config, true);
task.addOnSuccessListener(zone - > {
cloudDBZone = zone;
addSubscription();
}).addOnFailureListener(e - > toaster.sendErrorToast(context, e.getLocalizedMessage()));
}
Use userList​In the processResults method from last week we took the data snapshot and converted it into a list of User objects. We didn't then do anything with this nor did we have a method to pass that data onto somewhere else in the app. Lets change that!
We will start by creating a new public interface called UserCallBack this will be implemented by any class that wants to use an instance of the CloudDBManager. Currently the interface is pretty simple with just three methods:
Java:
public interface UserCallBack {
void onAddOrQuery(List < User > userList);
void onDelete(List < User > userList);
void onError(String errorMessage);
}
We create a variable for this Call back within the CloudDBManager like:
Java:
private final UserCallBack callBack;
And finally as part of the CloudDBManager init method we require an instance of the UserCallBack
Java:
public CloudDBManager(Context context, UserCallBack callBack) {
cloudDB = AGConnectCloudDB.getInstance();
this.context = context;
this.callBack = callBack;
}
Now when we have processed the result of a query we can pass that data back to the calling class using the callback. For example the processResult method now looks like:
Java:
private void processResults(CloudDBZoneSnapshot < User > userCloudDBZoneSnapshot) {
CloudDBZoneObjectList < User > userCursor = userCloudDBZoneSnapshot.getSnapshotObjects();
List < User > userList = new ArrayList < > ();
try {
while (userCursor.hasNext()) {
User user = userCursor.next();
updateMaxUserID(user);
userList.add(user);
}
callBack.onAddOrQuery(userList);
} catch (AGConnectCloudDBException e) {
callBack.onError(e.getLocalizedMessage());
toaster.sendErrorToast(context, e.getLocalizedMessage());
} finally {
userCloudDBZoneSnapshot.release();
}
}
We now have a way to both pass back the user list and also any error message that might be given during the processing!
The final CloudDBManager should look something like:
A manager class for the AGC CloudDB service
A manager class for the AGC CloudDB service. GitHub Gist: instantly share code, notes, and snippets.
gist.github.com
Authentication Manager​With the changes made above lets take a look at how we can use this to gain access to the database data. Specifically within the `AuthenticationManager`.
First we must now implement the new interface like so:
Java:
public class AuthenticationManager implements CloudDBManager.UserCallBack{
...
@Override
public void onAddOrQuery(List<User> userList) {
}
@Override
public void onDelete(List<User> userList) {
}
@Override
public void onError(String errorMessage) {
}
...
}
The onAddOrQuery method will be given the userList returned when a snapshot is processed, we can implement code here to update the UI or confirm if a user is already registered for example.
In this Classes init method we will also need to pass itself in as the UserCallBack. The setup of the CloudDBManager object will now look like:
Java:
dbManager = new CloudDBManager(context, this);
dbManager.createObjectType();
dbManager.openCloudDBZoneV2();
We are now in a good position to query data, view it and upsert it! Join us next week when we start configuring the other Objects we are going to be using, expand the User object and make the CloudDBManager generic for any CloudDB Object!
devwithzachary said:
Part 0 - Why?
Part 1 - Auth
Part 2 - CloudDB
Over the next couple of months I will be releasing a complete end to end guide to creating an Android app using serverless functionality to completely remove the need for any backend server/hosting etc.
These guides will be made up of a weekly live stream which will then be edited into a YouTube video along with each having a blog post for those that prefer a written guide!
But before we get into the actual guide (which will start with 'Part 1' next week) lets start from the... well start! Why might someone want to build a serverless app? what IS a serverless app? and what does Huawei's AppGallery Connect have to do with it?
Well I'm glad you asked!
What is a serverless app?​If Cloud computing takes away the need to manage the hardware, serverless computing takes away the need to manage the software. Its an extension of cloud computing where the provider handles everything about the servers and simply provides some kind of interface for the user to access their services. This might be in the form of an API, SDK, GUI or all of the above!
Why would I want to build a serverless app?​Server management is in its own right a full time job, from setting up the environment to installing and managing the software stack. Security updates, security hardening, authentication (to name a few) are all things that need to be managed in a traditional backend server setup. By using a serverless service all of this management work is removed, you as the app developer simply have access to the resources you need when you need them.
A couple of examples of why or when you might use a serverless system:
Example one - Prototyping​If you need to build an application prototype quickly, you want something that is just going to work and don't want the hassle of setting everything up! By using a serverless system you have instant access to the services you need, this lets you focus on prototyping the app itself.
devwithzachary said:
Part 0 - Why?
Part 1 - Auth
Part 2 - CloudDB
Over the next couple of months I will be releasing a complete end to end guide to creating an Android app using serverless functionality to completely remove the need for any backend server/hosting etc.
These guides will be made up of a weekly live stream which will then be edited into a YouTube video along with each having a blog post for those that prefer a written guide!
But before we get into the actual guide (which will start with 'Part 1' next week) lets start from the... well start! Why might someone want to build a serverless app? what IS a serverless app? and what does Huawei's AppGallery Connect have to do with it?
Well I'm glad you asked!
What is a serverless app?​If Cloud computing takes away the need to manage the hardware, serverless computing takes away the need to manage the software. Its an extension of cloud computing where the provider handles everything about the servers and simply provides some kind of interface for the user to access their services. This might be in the form of an API, SDK, GUI or all of the above!
Why would I want to build a serverless app?​Server management is in its own right a full time job, from setting up the environment to installing and managing the software stack. Security updates, security hardening, authentication (to name a few) are all things that need to be managed in a traditional backend server setup. By using a serverless service all of this management work is removed, you as the app developer simply have access to the resources you need when you need them.
A couple of examples of why or when you might use a serverless system:
Example one - Prototyping​If you need to build an application prototype quickly, you want something that is just going to work and don't want the hassle of setting everything up! By using a serverless system you have instant access to the services you need, this lets you focus on prototyping the app itself.
Example two - Basic requirements​Many apps have very basic backend server requirements. Perhaps they just want to store users details and setting preferences. Or maybe they just need a way to host and download files. These kinds of requirements tend to mean that a full managed backend server is overkill. When your requirements are simple no one wants to spend hours setting it up and managing a server!
Example Three - Small Team/Limited knowledge​If your a small team (or solo) you might simply not have the knowledge or man power to manage servers. The time taken to learn and maintain that knowledge might significantly impact the amount of time you have to develop your application. Sometimes its just much more cost effective to let another company manage this.
What does Huawei's AppGallery Connect have to do with it?​As part of Huawei's AppGallery platform they now offer a wide range of serverless features and functionality. These services come under the AppGallery Connect suite, including but not limited to, database, web hosting, authentication and storage. These services include generous free tiers which make prototyping and developing using these services even more attractive and cost effective.
Because of this we will be using this platform throughout the development guides as we explore what can be done with a serverless Android app!
Click to expand...
Click to collapse
Example two - Basic requirements​Many apps have very basic backend server requirements. Perhaps they just want to store users details and setting preferences. Or maybe they just need a way to host and download files. These kinds of requirements tend to mean that a full managed backend server is overkill. When your requirements are simple no one wants to spend hours setting it up and managing a server!
Example Three - Small Team/Limited knowledge​If your a small team (or solo) you might simply not have the knowledge or man power to manage servers. The time taken to learn and maintain that knowledge might significantly impact the amount of time you have to develop your application. Sometimes its just much more cost effective to let another company manage this.
What does Huawei's AppGallery Connect have to do with it?​As part of Huawei's AppGallery platform they now offer a wide range of serverless features and functionality. These services come under the AppGallery Connect suite, including but not limited to, database, web hosting, authentication and storage. These services include generous free tiers which make prototyping and developing using these services even more attractive and cost effective.
Because of this we will be using this platform throughout the development guides as we explore what can be done with a serverless Android app!
Click to expand...
Click to collapse
Useful sharing, thanks
Part 4 - Login and Register​Today we are going to look at getting the Login and Register process fully complete. This will include some refactoring to the code we have worked on before.
Authentication Manager​
With a CloudDBManager now in place that is able to handle the User object we created its time we make changes to the AuthenticationManager so that this CloudDBManager is correctly used to retrieve user data at login/register.
Firstly we have a number of variables that we might be passing into the AuthenticationManager. Up until this point we where only passing in a phone number or an email address and this was handled by the contactString variable. However now that we will be accepting registration information, more data needs to be accept.
When logging in the use may be using their mobile phone number or their email address. When registering they might provide either the phone number or email address or both, and in addition a username and display name.
With these elements in mind lets create a simple data object to store this and pass it into the AuthenticationManager as needed. This will look like below, with standard Getters/Setters and constructor.
Java:
public class LoginRegisterData {
String phoneNumber,email,username,displayName;
public LoginRegisterData(String phoneNumber, String email, String username, String displayName) {
this.phoneNumber = phoneNumber;
this.email = email;
this.username = username;
this.displayName = displayName;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
We will pass this data into the AuthenticationManager constructor like so:
Java:
public class AuthenticationManager implements CloudDBManager.UserCallBack{
Toaster toaster = new Toaster();
Context context;
int authType;
LoginRegisterData loginRegisterData;
boolean isLogin;
private final CloudDBManager dbManager;
private String loginUserUID = "0";
public AuthenticationManager(Context context, int authType, LoginRegisterData loginRegisterData, boolean isLogin){
this.context = context;
this.authType = authType;
this.loginRegisterData = loginRegisterData;
this.isLogin = isLogin;
dbManager = new CloudDBManager(context, this);
dbManager.createObjectType();
dbManager.openCloudDBZoneV2();
}
...
}
You will also notice that we have removed the contactString from the construct. As this variable has been removed we should also make sure to remove its usage and replace with the correct data from the LoginRegisterData object.
In places where we where expecting this string to contain the email address we should now use loginRegisterData.getEmail() and in places where we where expecting the phone number we should use loginRegisterData.getPhoneNumber().
Next lets take a look at the getUser() method. Up until now we have simply gotten the AGConnectUser for the currently authenticated user, however we haven't actually then done anything with that. Now we should use that authenticated user to get the stored User object from the database.
Java:
private void getUser(){
AGConnectUser user = AGConnectAuth.getInstance().getCurrentUser();
loginUserUID = user.getUid();
CloudDBZoneQuery<User> snapshotQuery = CloudDBZoneQuery.where(User.class).equalTo("uid", loginUserUID);
dbManager.queryUsers(snapshotQuery);
}
...
@Override
public void onQuery(List<User> userList) {
if (userList.size() == 1) {
User user = userList.get(0);
if (user.getUid().equals(loginUserUID)){
saveLoginDetail(user);
proceedToFeed();
}
}
}
Here we are getting the UID of the authenticated user and then querying the database for the user with that UID.
In the onQuery callback we can check that only one user was returned, and then triple check that the returned user does match the UID. From here we call two new methods saveLoginDetail() and proceedToFeed().
saveLoginDetail( ) is used to save a local copy of the logged in users ID and set a flag to say that we are now logged in. This way the next time the user opens the application we can check this flag and the user will not have to login every time they open the app.
Java:
private void saveLoginDetail(User user) {
SharedPreferences preferences = context.getSharedPreferences("loginDetail", 0);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("isLoginedIn", true);
editor.putInt("userId", user.getId());
}
The proceedToFeed() method will simply start the FeedActivity now that we are logged in.
Java:
private void proceedToFeed(){
context.startActivity(new Intent(context, FeedActivity.class));
}
From the Registration side of the process the only thing to change is the addition of being able to set the username and display name as below.
Java:
private void saveRegisteredUser(SignInResult signInResult){
User user = new User();
user.setId(dbManager.getMaxUserID() + 1);
user.setUid(signInResult.getUser().getUid());
user.setUsername(loginRegisterData.getUsername());
user.setDisplayname(loginRegisterData.getDisplayName());
dbManager.upsertUser(user);
}
In the onUpsert call back we use the same two methods saveLoginDetail() and proceedtoFeed() as the login process.
Java:
@Override
public void onUpsert(User user){
saveLoginDetail(user);
proceedToFeed();
}
And that's it! Your AuthenticationManager should now look like this: https://gist.github.com/devwithzachary/97e23d7c813fd917a13ab88de34f9751
Of course as we have now changed the constructor there are some changes that need to be made in both the login and register activities.
Login Activity​Within the MainActivity which is the login activity for us, lets start by creating a simple method to generate the LoginRegisterData object
Java:
private LoginRegisterData getLoginRegisterData() {
String emailString = email.getText().toString().trim();
String phoneString = phone.getText().toString().trim();
return new LoginRegisterData(phoneString, emailString, "", "");
}
As you can see we take the email and phone number input and build the object. At this point if we used this method in the phoneLogin and emailLogin onClick listeners we can see there is code duplication. So instead lets extract a method to trigger the login process.
Java:
private void login(int authType) {
authManager = new AuthenticationManager(MainActivity.this,
authType,
getLoginRegisterData(),
true);
authManager.sendVerifyCode();
}
As you can see we generate the AuthenticateManager passing in the LoginRegisterData and the authType. The OnClick Listeners for each button are now just one line calling this method and passing in the AuthType as needed. The MainActivity should now look like this: https://gist.github.com/devwithzachary/b9cb25f1b86b0503a5f4883345201b9d
Register Activity​For the register activity we do the same process, however we will also add two new EditText fields so that we can accept the user input for username and displayname. Otherwise the process is the same. Create the LoginRegisterData and pass that into the AuthenticationManager. This this in mind the Register activity will look something like: https://gist.github.com/devwithzachary/10a3837b0e9abbc32e1b3dcf083f4276
And that's it! we are now in a good state with the login and register flow which will result in a user being authenticated, logged in and use saving the ID of that user along with setting a flag to confirm the user is logged in.

Use Keyring for Sign-in Across Different Apps and Platforms

It's common for an app developer to develop multiple apps across different platforms. However, an issue that arises is that when signing in to different apps or the same app across different platforms on the same device, users usually needs to repeatedly enter their account name and password even if the same account is used, which has a negative impact on user experience.
Keyring provides developers with the ability to share user authentication credentials across different apps and app platforms, thus creating a seamless sign-in experience for users. In this article I will explain how to integrate Keyring and implement cross-platform & cross-app sign-in.
Preparations​
Before integrating the Keyring SDK, you'll need to perform the following preparations:
1. Configure app information in AppGallery Connect.
2. Create an Android Studio project and configure the Maven repository address. If you are using Android Studio, you can integrate the HMS Core SDK via the Maven repository. Before you start developing an app, integrate the HMS Core SDK into your Android Studio project.
3. Before building the APK, configure the obfuscation configuration file to prevent the HMS Core SDK from being obfuscated.
Integrating the Keyring SDK​
After completing the preparations, let's look at how to integrate the Keyring SDK.
1. Open the app-level build.gradle file in the project.
{
"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. Add a build dependency on the Keyring SDK in the dependencies block.
Find the dependencies block in the build.gradle file, add a dependency on the Keyring SDK in the block, and click Sync Now to make Android Studio synchronize the project.
Code:
dependencies{
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation "com.huawei.hms:keyring-credential:6.1.1.302"
}
Scenario Simulated by the Sample Code​
​The sample code provides two sample apps, which are App1 and App2.
App1 simulates the sign-in operation, and calls APIs of Keyring to save the user sign-in credentials. App1 then shares these credentials with App2.
App2 calls APIs of Keyring to query available credentials, and calls the getContent API to obtain the user's sign-in password.
To make the sample code work, we need to create two apps in AppGallery Connect, download the agconnect-services.json files of the two apps, and place the files in the corresponding Android Studio projects of App1 and App2. Refer to "Preparations" for details.
Preparing Information​
We need to collect the information listed below for use later on.
We need to set the sharing relationship in the code for App1 and App2. Therefore, we need to query and record the package names and signing certificate fingerprints of App1 and App2. For details about how to obtain the signing certificate fingerprint, please refer to "Preparations."
Development Practice​
1. Configure App1 to save user credentials using the Keyring SDK.
App1 simulates the app sign-in operation, the sample project has only one activity, that is, MainActivity, in which most code lines are used to implement the sign-in screen and basic sign-in logic. This activity has a member object of the CredentialClient type, which is the operation object for accessing Keyring and is initialized in the onCreate method of the activity.
Code:
mCredentialClient = CredentialManager.getCredentialClient(this);
Then, implement the login method for the onClick method of the Login button.
Code:
private void login(View view) {
if (!checkInput()) {
return;
}
String username = mUsername.getText().toString().trim();
String password = mPassword.getText().toString().trim();
// connect server to login.
saveCredential(username, password,
"app2", "com.huawei.hms.keyring.sample.app2",
"XX:XX:XX:XX:XX:XX",
true);
}
At the beginning of the login method, the checkInput method is called to check the validity of the user name and password in the input text boxes. In most cases, the service logic will connect to the background and use the user name and password for sign-in. The sample code does not include the code for implementing this. Here, we assume that the user name and password are correct, and that sign-in is performed successfully in the background.
The next step is to call the saveCredential method to save the credentials. The saveCredential method is a simple method encapsulated to highlight the input parameters. Such input parameters include the user name, password, information related to the sharing target app. It also includes the configuration specifying whether user authentication is required during credential reading.
App2's package name and certificate fingerprint recorded earlier will be used here. When calling the saveCredential method, we need to pass the package name and certificate fingerprint of App2 to the method as parameters.
Code:
private void saveCredential(String username, String password,
String sharedToAppName, String sharedToAppPackage,
String sharedToAppCertHash, boolean userAuth) {
AndroidAppIdentity app2 = new AndroidAppIdentity(sharedToAppName,
sharedToAppPackage, sharedToAppCertHash);
List<AppIdentity> sharedAppList = new ArrayList<>();
sharedAppList.add(app2);
Credential credential = new Credential(username,
CredentialType.PASSWORD, userAuth,
password.getBytes());
credential.setDisplayName("nickname_" + username);
credential.setSharedWith(sharedAppList);
credential.setSyncable(true);
mCredentialClient.saveCredential(credential, new CredentialCallback<Void>() {
@Override
public void onSuccess(Void unused) {showMessage("save credential to Keyring success");}
@Override
public void onFailure(long errorCode, CharSequence description) {
showMessage("save to Keyring failed" + " ====" + errorCode + ":" + description);
}
});
}
In the saveCredential method, we use the package name and certificate fingerprint of App2 to create an app object app2. Then, we place this object in sharedAppList of AppIdentity and use the list as a parameter of the credential object. After that, we create a password-type credential object, and call a series of set methods for this object to set the sharing app list and other configurations of the credentials. Finally, we call the saveCredential method of the Keyring operation object mCredentialClient to save the credentials. We also need to set the callback methods called when credentials are successfully saved or failed to be saved.
We have now saved the credentials in Keyring.
2. Configure App2 to use the Keyring SDK to query and read credentials.
App2 simulates the sign-in operation of another app made by the same developer. App2 can use the Keyring SDK to query credentials saved by itself earlier and credentials shared with it by the specified app, and read the credential content for seamless sign-in. App2 also has only one activity, that is, MainActivity, which simulates the sign-in screen. App2, like App1, also defines a member object mCredentialClient of the CredentialClient type and uses the object as the operation object for accessing Keyring. The operation object is also initialized in the onCreate method of the activity. The queryCredential method is called at the end of the onCreate method to query available credentials.
Now, let's look at how to implement the queryCredential method.
Code:
private void queryCredential() {
final AndroidAppIdentity app1 = new AndroidAppIdentity("app1",
"com.huawei.hms.keyring.sample.app1",
"XX:XX:XX:XX:XX:XX"
);
List<AppIdentity> trustedOwnerList = new ArrayList<>();
trustedOwnerList.add(app1);
mCredentialClient.findCredential(trustedOwnerList, new CredentialCallback<List<Credential>>() {
@Override
public void onSuccess(List<Credential> credentials) {
if (credentials.isEmpty()) {
noAvailableCredential();
} else {
MainActivity.this.setCredentialList(credentials);
}
}
@Override
public void onFailure(long errorCode, CharSequence description) {
noAvailableCredential();
}
private void noAvailableCredential(){showMessage("no shared credential");}
});
}
When preparing for SDK integration earlier, we recorded the package name and certificate fingerprint of App1. Now we use the information in the queryCredential method to create an AndroidAppIdentity object representing App1. Then, place the object in trustedOwnerList of AppIdentity.
Next, we call the findCredential method of the Keyring operation object mCredentialClient and pass trustedOwnerList and the result callback to the method. The findCredential method will query and list credentials that are available to this app. Such credentials include those stored by this app and those shared with this app by other apps specified in trustedOwnerList. In the sample code of App2, the findCredential method will query credentials saved by App2 and credentials shared with App2 by App1.
Available credentials exist if the onSuccess method is called and the credentials parameter is not empty. Then, the setCredentialList method will be called to display the credential list in the RecyclerView component on the app screen.
The choose method in the CredentialHolder class will be called if one of the credentials displayed on the screen is chosen. In the choose method, the selected credential object will be assigned to the member object mChooseCredential. After the user taps the Login button on the app screen, the login method will be called.
Code:
private void login(View v) {
if (mChooseCredential == null) {
showMessage("please_choose_account");
return;
}
mChooseCredential.getContent(new CredentialCallback<byte[]>() {
@Override
public void onSuccess(byte[] bytes) {
// String password = new String(bytes);
showMessage("Login success");
}
@Override
public void onFailure(long l, CharSequence charSequence) {
showMessage("Get password failed");
}
});
}
In the login method, if mChooseCredential is not empty, the getContent method of the chosen credential object will be called to obtain the credential content. When calling the getContent method, we also need to pass the result callback to it.
The bytes parameter in the onSuccess method indicates the credential content, that is, the user sign-in password saved by App1. In a real app, we can directly use the password for sign-in, creating a seamless sign-in experience.
That's all for this tutorial. You can visit the Keyring official website for more information.
Does it support for quickApp?
Basavaraj.navi said:
Does it support for quickApp?
Click to expand...
Click to collapse
Yes, it supports quick apps.

Categories

Resources