Introduction
Huawei provides various services for developers to make ease of development and provides best user experience to end users. In this article, we will cover Thread Management with Java in Harmony OS.
Thread is a lightweight process allows a program to operate more efficiently by doing multiple things at the same time. Threads can be used to perform complicated tasks in the background without interrupting the main program.
The system creates a main thread for an application at runtime. The main thread is created or deleted in accordance with the application, so it is regarded as the core thread for an application. All UI-specific operations, such as UI display and update, are running in the main thread. Therefore, the main thread is also called the UI thread. By default, all operations of an application run in the main thread. If there are time-consuming tasks required by the application, such as downloading files and querying the database, you can create other threads to execute such tasks.
When to Use
If an application contains complex service logic, you may need to create multiple threads to execute various tasks, which causes complex interactions between tasks and threads. This may result in more complicated code and higher maintenance cost. To avoid such issues, you can utilize TaskDispatcher to optimize the dispatch of different tasks.
Available APIs
TaskDispatcher is the basic API for Ability instances to dispatch tasks, and it hides the implementation details of the thread where the task is located. TaskDispatcher. By default, tasks running in the UI thread have higher priorities, and tasks without the need of any results to return usually have lower priorities.
{
"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"
}
We have multiple type of TaskDispatcher major type of task dispatcher are as follows.
1. GlobalTaskDispatcher
The global task dispatcher is obtained by an ability by calling getGlobalTaskDispatcher().
Code:
TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
2. ParallelTaskDispatcher
The parallel task dispatcher is created and returned by an ability by calling createParallelTaskDispatcher().
Code:
String dispatcherName = "parallelTaskDispatcher";
TaskDispatcher parallelTaskDispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
3. SerialTaskDispatcher
The serial task dispatcher is created and returned by an ability by calling createSerialTaskDispatcher().
Code:
String dispatcherName = "serialTaskDispatcher";
TaskDispatcher serialTaskDispatcher = createSerialTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
4. SpecTaskDispatcher
The dedicated task dispatcher is dedicated to a specific thread, which currently refers to the UI thread. Tasks in the UI thread are dispatched using the UITaskDispatcher.
Code:
TaskDispatcher uiTaskDispatcher = getUITaskDispatcher();
Development Overview
You need to install DevEcho studio IDE and I assume that you have prior knowledge about the Harmony OS and java.
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 installation package.
DevEcho studio installed.
HMS Core (APK) 4.X or later.
Follows the steps.
1. Create Unity Project.
Open DevEcho studio.
Click NEW Project, select a Project Template.
Select ability template and click Next as per below image.
Enter Project and Package Name and click on Finish.
2. Once you have created the project, DevEco Studio will automatically sync it with Gradle files. Find the below image after synchronization is successful.
3. Update Permission and app version in config.json file as per your requirement, otherwise retain the default values.
4. Create New Ability as follows.
5. Development Procedure.
Create new Ability MainAbilitySlice.java
Java:
package com.hms.multithread.slice;
import com.hms.multithread.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Button;
import ohos.agp.components.Text;
import ohos.app.dispatcher.Group;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.app.dispatcher.task.TaskPriority;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class MainAbilitySlice extends AbilitySlice {
HiLogLabel LABEL_LOG;
final long delayTime = 5L;
Text result;
String resultString;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
result = (Text) findComponentById(ResourceTable.Id_result);
Button globalDispatcher = (Button) findComponentById(ResourceTable.Id_global_disp);
Button parallelDispatcher = (Button) findComponentById(ResourceTable.Id_parallel_disp);
Button uiThreadDispatcher = (Button) findComponentById(ResourceTable.Id_ui_thread_disp);
// Set a click event listener for the button.
globalDispatcher.setClickedListener((listener -> present(new GlobleTaskDispatcher(), new Intent())));
parallelDispatcher.setClickedListener(component -> startParallelThread());
uiThreadDispatcher.setClickedListener(component -> startUIThread());
LABEL_LOG = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG");
}
private void startParallelThread() {
resultString="// The execution result may be as follows:";
String dispatcherName = "parallelTaskDispatcher";
TaskDispatcher dispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
// Create a task group for group dispatch.
Group group = dispatcher.createDispatchGroup();
// Add a task (task1) to the group and return an instance used to revoke this task.
dispatcher.asyncGroupDispatch(group, () -> {
resultString=resultString+"\n"+"//download task1 is running";
HiLog.info(LABEL_LOG, "download task1 is running");
});
// Add task2 (associated with task1) to the group.
dispatcher.asyncGroupDispatch(group, () -> {
resultString=resultString+"\n"+"//download task1 is running";
HiLog.info(LABEL_LOG, "download task2 is running");
});
// Close the application after executing all tasks in the task group.
dispatcher.groupDispatchNotify(group, () -> {
resultString=resultString+"\n"+"//the close task is running after all tasks in the group are completed";
//result.setText(resultString);
HiLog.info(LABEL_LOG, "the close task is running after all tasks in the group are completed");
//result.setText("Update UI" + resultString);
});
}
private void startUIThread() {
TaskDispatcher uiTaskDispatcher = getUITaskDispatcher();
// Create a task group for group dispatch.'
Group group = uiTaskDispatcher.createDispatchGroup();
uiTaskDispatcher.delayDispatch(() -> result.setText("Hi Lokesh Kumar this is UI Thread"), delayTime);
// Add task2 (associated with task1) to the group.
uiTaskDispatcher.asyncGroupDispatch(group, () -> HiLog.info(LABEL_LOG, "download task2 is running"));
uiTaskDispatcher.asyncGroupDispatch(group, () -> HiLog.info(LABEL_LOG, "download task3 is running"));
// Close the application after executing all tasks in the task group.
uiTaskDispatcher.groupDispatchNotify(group, () -> HiLog.info(LABEL_LOG, "the close task is running after all tasks in the group are completed"));
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
}
The following snippet shows how to use a GlobalTaskDispatcher to perform a synchronous dispatch:
private void syncDispather() {
TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
//Group group = globalTaskDispatcher.createDispatchGroup();
stringBuffer=new StringBuffer();
stringBuffer.append("\n");
globalTaskDispatcher.syncDispatch(() -> {
HiLog.info(LABEL_LOG, "sync task1 run");
stringBuffer.append("// sync task1 run");
});
stringBuffer.append("\n");
HiLog.info(LABEL_LOG, "after sync task1");
stringBuffer.append("// after sync task1");
stringBuffer.append("\n");
globalTaskDispatcher.syncDispatch(() -> {
HiLog.info(LABEL_LOG, "sync task2 run");
stringBuffer.append("// sync task2 run");
});
stringBuffer.append("\n");
HiLog.info(LABEL_LOG, "// after sync task2");
stringBuffer.append("// after sync task2");
globalTaskDispatcher.syncDispatch(() ->{
HiLog.info(LABEL_LOG, "sync task3 run");
stringBuffer.append("// sync task3 run");
});
stringBuffer.append("\n");
HiLog.info(LABEL_LOG, "after sync task3");
stringBuffer.append("// after sync task3");
result.setText("Result"+ stringBuffer );
}
The following code snippet shows how to execute a task for multiple times:
private void countDownLeach() {
final int total = 10;
final CountDownLatch latch = new CountDownLatch(total);
final List<Long> indexList = new ArrayList<>(total);
TaskDispatcher dispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
// Execute the task multiple times, as specified by the parameter total.
dispatcher.applyDispatch((index) -> {
indexList.add(index);
latch.countDown();
}, total);
// Set the task timeout.
try {
latch.await();
} catch (InterruptedException exception) {
HiLog.error(LABEL_LOG, "latch exception");
}
HiLog.info(LABEL_LOG, "list size matches, %{public}b", (total == indexList.size()));
StringBuffer sb=new StringBuffer();
// result.setText("list size matches, %{public}b"+" "+ (total == indexList.size()));
sb.append("Execute the task multiple times as per below").append("\n");
for (Long i: indexList){
sb.append(i+" ");
if(i%4==0)
sb.append("\n");
}
result.setText(String.valueOf(sb));
// The execution result is as follows:
// list size matches, true
}
Create a layout file under entry > src > main > resources > base > layout > ability_main.xml
XML:
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:alignment="top"
ohos:orientation="vertical">
<Text
ohos:id="$+id:text_time"
ohos:height="match_content"
ohos:width="match_content"
ohos:top_margin="200vp"
ohos:background_element="$graphic:background_ability_main"
ohos:layout_alignment="horizontal_center"
ohos:text="$string:mainability_thread_management"
ohos:text_size="36fp"
/>
<Button
ohos:id="$+id:global_disp"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text_size="25fp"
ohos:padding="10vp"
ohos:start_margin="20vp"
ohos:end_margin="20vp"
ohos:top_margin="90vp"
ohos:bottom_margin="20vp"
ohos:background_element="$graphic:background_button"
ohos:text="$string:mainability_globle_thread"/>
<Button
ohos:id="$+id:parallel_disp"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text_size="25fp"
ohos:padding="10vp"
ohos:margin="20vp"
ohos:background_element="$graphic:background_button"
ohos:text="$string:mainability_parallel_thread"/>
<Button
ohos:id="$+id:ui_thread_disp"
ohos:height="match_content"
ohos:width="match_parent"
ohos:text_size="25fp"
ohos:padding="10vp"
ohos:margin="20vp"
ohos:background_element="$graphic:background_button"
ohos:text="$string:mainability_ui_thread"/>
<Text
ohos:id="$+id:result"
ohos:height="match_content"
ohos:width="match_content"
ohos:top_margin="20vp"
ohos:background_element="$graphic:background_ability_main"
ohos:layout_alignment="horizontal_center"
ohos:text="$string:mainability_result"
ohos:text_size="35vp"
/>
</DirectionalLayout>
6. To build apk and run in device, choose Build > Generate Key and CSR Build for Hap(s)\ APP(s) or Build and Run into connected device, follow the steps.
Result
1. Click on UI Thread|SpecTaskDispatcher Button. It’s bound to the main thread of an application and send result back to main thread and update UI as per below screen.
Pros: Its will update result in UI thread.Tasks in the UI thread are dispatched using the UITaskDispatcher.
For example if you want fetch data form server and update in UI then you can use this method.
Cons: If you trying to update UI other than UI thread its will throw “attempt to update UI in non-UI thread” exception
2. Click on Global Task Dispatcher Button. Its will navigate into other screen, then click on respective button you can separate result.
3. Click on Sync Dispach Button the syncDispatch method dispatches a task synchronously and waits for the task execution in the current thread. The current thread remains blocked until the execution result is returned. As per below result.
Pros : The syncDispatch method will execute all task synchronously. The current thread remains blocked until the execution result is returned. All synchronized blocks synchronized on the same object can only have one thread executing inside them at a time.
Cons: If syncDispatch is used incorrectly, a deadlock will occur.
4. Click on delayDispatch button the applyDispatch executes a specified task on multiple times.
Pros: The delayDispatch method asynchronously dispatches a task with delay and proceeds to the next operation immediately. You can delay your task as per your requirement.
For example if you want execute a method A after 10 second of method B then you can use this delayDispatcher and complete your task easily.
Cons: If delayDispatch is used incorrectly, its will block your script or can throw ANR.
pros:- The applyDispatch executes a specified task multiple times. As per your requirement you can use applyDispatch method to execute a task multiple time like as fetching data from list of data from database or server.
Cons:- If applyDispatch is used incorrectly, its will execute your script infinitely and can block your UI and other resources
Tips and Tricks
Always use the latest version of DevEcho Studio.
Use Harmony Device Simulator from HVD section.
Conclusion
In this article, we have learnt Thread Management in Harmony OS. If an application contains complex service logic, you may need to create multiple threads to execute various tasks, which causes intricate interactions between tasks and threads. This may result in more complicated code and higher maintenance cost. To avoid such issues, you can utilize TaskDispatcher to optimize the dispatch of different tasks.
Thanks for reading the article, please do like and comment your queries or suggestions.
References
Harmony OS: https://www.harmonyos.com/en/develop/
Harmony OS Thread Management: https://developer.harmonyos.com/en/docs/documentation/doc-guides/thread-mgmt-overview-0000000000032127
Original Source
how unused objects are detroying via garbage collector?
Related
Have you ever wanted to be able to scan and create 13+ different types of barcodes from your app? Hopefully you have, otherwise I'm not sure why you're reading this. If you do, then Huawei has an SDK for you: Scan Kit.
Scan Kit allows you to scan and generate various different one-dimensional and two-dimensional barcodes with ease, and you don't even need a Huawei device to use it.
Interested? No? Well, get interested. Now? Good. Let's get started.
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 Scan Kit 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 one of the following to the "dependencies" section of your app-level build.gradle file:
Code:
implementation 'com.huawei.hms:scan:1.2.0.301'
implementation 'com.huawei.hms:scanplus:1.2.0.301'
The difference between these dependencies is fairly simple. The basic Scan Kit SDK is only about 0.8MB in size, while the Scan Kit Plus SDK is about 3.3MB. The basic SDK will function identically to the Plus SDK on Huawei devices. On non-Huawei devices, you'll lose the enhanced recognition capabilities with the basic SDK.
If your app has size restrictions, and you only need basic QR code recognition, then the basic SDK is the best choice. Otherwise, pick the Plus SDK.
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, 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 Scan Kit SDK should now be available in your project.
Basic Usage
In order to properly use Scan Kit, you'll need to declare some permissions in your app:
XML:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
These are both dangerous-level permissions, so make sure you request access to them at runtime on Marshmallow and later.
You should also declare a couple required device features, to make sure distribution platforms can hide your app on incompatible devices:
XML:
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
Now that you've got the permissions and features declared, it's time to start scanning. There are four modes you can use: Default View, Customized View, Bitmap, and MultiProcessor.
Default View
The Default View is the quickest way to get set up and running. Scan Kit provides the UI and does all the scanning work for you. This mode is able to scan barcodes live through the camera viewfinder, or by scanning existing images.
The first thing you'll need to do to use this is to declare the Scan Kit's scanner Activity in your Manifest:
XML:
<activity android:name="com.huawei.hms.hmsscankit.ScanKitActivity" />
Next, simply call the appropriate method to start the Activity. This must be done from an Activity context, since it makes use of Android's onActivityResult() API.
Code:
//The options here are optional. You can simply pass null
//to the startScan() method below.
//This is useful if you're only looking to scan certain
//types of codes. Scan Kit currently supports 13. You can
//see the list here: https://developer.huawei.com/consumer/en/doc/HMSCore-Guides-V5/barcode-formats-supported-0000001050043981-V5
val options = HmsScanAnalyzerOptions.Creator()
.setHmsScanTypes(
HmsScan.QRCODE_SCAN_TYPE,
HmsScan.DATAMATRIX_SCAN_TYPE
)
.create()
ScanUtil.startScan(activity, REQUEST_CODE, options)
Now you just need to handle the result:
Code:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != Activity.RESULT_OK || data == null) {
//Scanning wasn't successful. Nothing to handle.
return
}
if (requestCode == REQUEST_CODE) {
//Scan succeeded. Let's get the data.
val hmsScan = data.getParcelableExtra<HmsScan>(ScanUtil.RESULT)
if (hmsScan != null) {
//Handle appropriately.
}
}
}
We'll talk more about handling the result later.
Customized View
Customized View is similar to Default View in that Scan Kit will handle the scanning logic for you. The difference is that this mode lets you create your own UI. You're also limited to using the live viewfinder.
The following code should be placed inside your Activity's onCreate() method:
Code:
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scan)
//A ViewGroup to hold the scanning View that HMS will create
val container = findViewById<FrameLayout>(R.id.scanning_container)
//The position/size of the scanning area inside the scanning
//View itself. This is optional.
val scanBounds = Rect()
//The scanning View itself.
val scanView = RemoteView.Builder()
.setContext(this)
.setBoundingBox(scanBounds) //optional
.setFormat(HmsScan.QR_SCAN_TYPE) //optional, accepts a vararg of formats
.build()
//Initialize the scanning View.
scanView.onCreate(savedInstanceState)
container.addView(scanView)
scanView.setOnResultCallback { result ->
//`result` is an Array<HmsScan>
//Handle accordingly.
}
}
We'll talk about how to handle the HmsScan object later.
In a real application, the "scanView" variable should be a global variable. It's lifecycle-aware, so it has all the lifecycle methods, which you should probably call:
Code:
override fun onStart() {
super.onStart()
scanView.onStart()
}
//etc
Bitmap
This mode is useful if you have a Bitmap and you want to decode it. You can get that Bitmap however you want. All that matters is that you have one.
The example below shows how to obtain a Bitmap either from a camera capture frame or from an existing image in internal storage, and how to pass that Bitmap to Scan Kit for processing.
Code:
val img = YuvImage(
//A byte array representation of an image.
data,
//Depends on your image type: https://developer.android.com/reference/android/graphics/ImageFormat
ImageFormat.NV21,
//The width of the image data.
width,
//The height of the image data.
height
)
val stream = ByteArrayOutputStream()
//Copy the data to an output stream.
img.compressToJpeg(
//Size of the JPEG, in a Rect representation.
Rect(0, 0, width, height),
//Quality.
100,
//Output stream.
stream
)
//Create a Bitmap
val bmp = BitmapFactory.decodeByteArray(
stream.asByteArray(),
0,
stream.asByteArray().size
)
//If you want to use a pre-existing image, just obtain
//a Bitmap for that. This example would be placed in
//onActivityResult() after using ACTION_GET_CONTENT for an image.
val bmp = MediaStore.Images.Media.getBitmap(contentResolver, data.data)
//Set up the options.
val options = HmsScanAnalyzerOptions.Creator()
//Scan types are optional.
.setHmsScanTypes(
HmsScan.QRCODE_SCAN_TYPE,
HmsScan.DATAMATRIX_SCAN_TYPE
)
//If you're retrieving a Bitmap from an existing photo,
//set this to `true`.
.setPhotoMode(false)
.create()
//Send the scanning request and get the result.
val scans: Array<HmsScan> = ScanUtil.decodeWithBitmap(context, bmp, options)
if (!scans.isNullOrEmpty()) {
//Scanning was successful.
//Check individual scanning results.
scans.forEach {
//Handle.
//If zoomValue != 1.0, make sure you update
//the focal length of your camera interface,
//if you're using a camera. See Huawei's
//documentation for details on this.
//https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/android-bitmap-camera-0000001050042014
val zoomValue = it.zoomValue
}
}
We'll go over how to handle the HmsScan later on.
MultiProccessor
The MultiProcessor mode supports both existing images and using a viewfinder. It can take image frames and analyze them using machine learning to simultaneously detect multiple barcodes, along with other objects, like faces.
There are two ways to implement this mode: synchronously, and asychronously.
Here's an example showing how to implement it synchronously.
Code:
//Create the options (optional).
val options = HmsScanAnalyzerOptions.Creator()
.setHmsScanTypes(
HmsScan.QRCODE_SCAN_TYPE,
HmsScan.DATAMATRIX_SCAN_TYPE
)
.create()
//Set up the barcode detector.
val barcodeDetector = HmsScanAnalyzer(options /* or null */)
//You'll need some sort of Bitmap reference.
val bmp = /* However you want to obtain it */
//Convert that Bitmap to an MLFrame.
val frame = MLFrame.fromBitmap(bmp)
//Analyze and handle result(s).
val results: SparseArray<HmsScan> = barcodeDetector.analyseFrame(frame)
results.forEach {
//Handle accordingly.
}
To analyze asynchronously, just change the method called on "barcodeDetector":
Code:
barcodeDetector.analyzInAsyn(frame)
.addOnSuccessListener { scans ->
//`scans` is a List<HmsScan>
scans?.forEach {
//Handle accordingly.
}
}
.addOnFailureListener { error ->
//Scanning failed. Check Exception.
}
Parsing Barcodes
It's finally time to talk about parsing the barcodes! Yay!
The HmsScan object has a bunch of methods you can use to retrieve information about barcodes scanned by each method.
Code:
val scan: HmsScan = /* some HmsScan instance */
//The value contained in the code. For instance,
//if it's a QR code for an SMS, this value will be
//something like: "smsto:1234567890:Hi!".
val originalValue = scan.originalValue
//The type of code. For example, SMS_FORM, URL_FORM,
//or WIFI_CONNTENT_INFO_FORM. You can find all the forms
//here: https://developer.huawei.com/consumer/en/doc/development/HMSCore-References-V5/scan-hms-scan4-0000001050167739-V5.
val scanTypeForm = scan.scanTypeForm
//If it's a call or SMS, you can retrieve the
//destination phone number. This will be something
//like "1234567890".
val destPhoneNumber = scan.destPhoneNumber
//If it's an SMS, you can retrieve the message
//content. This will be something like "Hi!".
val smsContent = scan.smsContent
There are plenty more methods in the HmsScan class to help you parse various barcodes. You can see them all in Huawei's API reference.
Barcode Generation
Finally, let's talk about how to make your own barcodes using Scan Kit. This example will show you how to make a QR code, although you should be able to create any of the formats supported by Scan Kit.
Code:
//The code's content.
val content = "Some Content"
//Can be any supported type.
val type = HmsScan.QRCODE_SCAN_TYPE
//Output dimensions.
val width = 400
val height = 400
//Create the options.
val options = HmsBuildBitmapOption.Creator()
//The background of the barcode image. White is the default.
.setBitmapBackgroundColor(Color.RED)
//The color of the barcode itself. Black is the default
.setBitmapColor(Color.BLUE)
//Margins around the barcode. 1 is the default.
.setBitmapMargin(3)
.create()
try {
//Build the output Bitmap.
val output = ScanUtil.buildBitmap(content, type, width, height, options)
//Save it to internal storage, upload it somewhere, etc.
} catch (e: WriterException) {
//Creation failed. This can happen if one of the arguments for `buildBitmap()` is invalid.
}
Conclusion
And that's it!
Scan Kit is a powerful SDK that doesn't even require a Huawei device to work. Hopefully, this guide helped you get up and running. For more details on implementation, along with the API reference, be sure to check out Huawei's full documentation.
The HMS Site Kit SDK is a handy set of APIs to help you implement location and address-based searching in your app with ease. If you want to target Huawei devices with your app, and you're looking for a simple way to find and show results to your user, you've come to the right place.
This guide will get you started with the Site Kit SDK, and give you some basic implementation examples. Let's get started.
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 Site Kit 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:site:5.0.0.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 "Site Kit" 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 Site Kit SDK should now be available in your project.
Basic Usage
Huawei's Site Kit SDK gives you quite a few useful features.
There's Keyword Search, which lets the user type in a search query, along with other options, and see relevant results.
There's Nearby Place Search, which returns points of interest close to the user. There's a Place Details API, to let the user find out more about a specific place.
There's the Place Search Suggestion feature, which adds autofill-like search suggestions in real-time.
And finally, there's the Widget component, which implements Place Search Suggestion in a search bar for you.
We'll go over each feature one by one.
Keyword Search
Below is an example of how you might implement a keyword search.
Code:
//Create the search service. It's recommended you use an Activity
//Context, although it's not required.
//You can find your API key on the Project Setting page for your
//project in AppGallery Connect, under App Information.
val searchService = SearchServiceFactory.create(context, "API_KEY")
//Create a request. For this example, we're going to be
//using dummy inputs, but in a real app, these would
//be provided by the user.
val request = TextSearchRequest()
//This can be any keyword, like "McDonald's" or
//"Washington".
request.setQuery("London")
//Define a location (optional). This could be the user's
//current location, or some other area.
val location = Coordinate(48.893478, 2.334595)
request.setLocation(location)
//Set a radius in meters (optional). The default is 50,000.
location.setRadius(1000)
//Set the type of location (optional, but recommended).
//Values can be found in the HwLocationType class.
request.setHwPoiType(HwLocationType.ENTERTAINMENT_PLACE)
//Set the country code to where these results should be
//confined (optional). Uses the ISO-3166-1 alpha-2 standard.
request.setCountryCode("FR")
//Set the language for results to appear in (optional).
request.setLanguage("fr")
//Set the current page (from 1-60). Default is 1.
request.setPageIndex(1)
//Set how many results should appear per-page (from 1-20).
//Default is 20.
request.setPageSize(5)
//Create a result listener for handling search results.
val resultListener = object : SearchResultListener<TextSearchResponse>() {
override fun onSearchResult(result: TextSearchResponse?) {
//We've got results.
if (result == null || result.totalCount <= 0) {
//We actually don't have results. Return.
return
}
val sites: List<Site>? = result.sites
if (sites.isNullOrEmpty()) {
//Couldn't find any sites. Return.
return
}
//Handle results.
sites.forEach { site ->
val id = site.siteId
val name = site.name
}
}
override fun onSearchError(state: SearchStatus) {
//There was an error retrieving results.
val code = status.errorCode
val message = status.errorMessage
}
}
//Finally, initiate the search.
searchService.textSearch(request, resultListener)
Nearby Place Search
A Nearby Place Search is practically identical to a Keyword Search in terms of implementation. Simply replace the textSearch() call with a nearbySearch() call. Almost everything else is identical.
Code:
//Use this instead of `textSearch()`.
//One notable difference is that the default radius
//for a nearby search is 1,000 meters instead of 50,000.
searchService.nearbySearch(request, resultListener)
Place Details
Creating a Place Details request is similar to the previous two, although there are some differences in implementation. For one, you should know the site ID of the place whose details you're querying, which you can get using one of the above methods.
Code:
/Create the search service. It's recommended you use an Activity
//Context, although it's not required.
//You can find your API key on the Project Setting page for your
//project in AppGallery Connect, under App Information.
val searchService = SearchServiceFactory.create(context, "API_KEY")
//Create the details request.
val request = DetailSearchRequest()
//Set the site ID. You can retrieve this from a keyword or
//nearby place search result.
request.setSiteId("THE_SIDE_ID")
//Set the language (optional).
request.setLanguage("fr")
//Create a results listener.
val resultListener = object : SearchResultListener<DetailSearchResponse>() {
override fun onSearchResult(result: DetailSearchResponse?) {
//Detail search succeeded.
if (result == null) {
//Something weird happened. Return.
return
}
//Retrieve the site. Return if null.
val site = result.site ?: return
//Retrieve various details about the site and display
//for the user.
}
override fun onSearchError(status: SearchStatus) {
//There was an error retrieving results.
val code = status.errorCode
val message = status.errorMessage
}
}
//Initiate the request.
searchService.detailSearch(request, resultListener)
Place Search Suggestion
This feature is similar to the Keyword and Nearby Search features, except that it returns a limited set of results. It's more suitable for rapid-fire requests, such as real-time search suggestions.
Here's an example for how to implement it.
Code:
/Create the search service. It's recommended you use an Activity
//Context, although it's not required.
//You can find your API key on the Project Setting page for your
//project in AppGallery Connect, under App Information.
val searchService = SearchServiceFactory.create(context, "API_KEY")
//Create the request.
val request = QuerySuggestionRequest()
//Set the user's current query.
request.setQuery("Pari")
//Set a location (optional).
val location = Coordinate(48.893478, 2.334595)
request.setLocation(location)
//Set a radius in meters (optional) (from 1-50,000).
//Default is 50,000.
request.setRadius(50)
//Set the search country (optional).
request.setCountryCode("FR")
//Set the language results should appear in
//(optional).
request.setLanguage("fr")
//Set a specific POI type (optional). Should
//be a subset of the values found in LocationType.
request.setPoiTypes(LocationType.ADDRESS)
//Create a results listener.
val resultListener = object : SearchResultListener<QuerySuggestionResponse>() {
override fun onSearchResult(result: QuerySuggestionResponse?) {
if (result == null) {
//No results. Return.
return
}
val sites: List<Site>? = result.sites
if (sites.isNullOrEmpty()) {
//No sites. Return.
return
}
//Handle results.
sites.forEach { site ->
val id = site.sideId
val name = site.name
}
}
override fun onSearchError(status: SearchStatus) {
//There was an error retrieving results.
val code = status.errorCode
val message = status.errorMessage
}
}
//Initiate the search request.
searchServie.querySuggestion(request, resultListener)
Widget
The Site Kit can take care of search logic for you, including suggestions. Here's how.
The first thing you need to do is implement the Fragment in your layout.
XML:
<fragment
android:id="@+id/search_fragment"
android_name="com.huawei.hms.site.widget.SearchFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
You can also create a reference in code and use a FragmentManager to add and remove it as needed.
Next, you need to implement the search functionality.
Code:
//Get a reference to the Fragment. This example assumes it's
//in your layout XML.
val searchFragment = supportFragmentManager.findFragmentById(R.id.search_fragment) as SearchFragment
//Set the API key.
//You can find your API key on the Project Setting page for your
//project in AppGallery Connect, under App Information.
searchFragment.setApiKey("API_KEY")
//Set a selection listener.
searchFragment.setOnSiteSelectedListener(object : SiteSelectionListener() {
override fun onSiteSelected(site: Site) {
//The user has selected a site returned by HMS.
//Handle as applicable.
}
override fun onError(status: SearchStatus) {
//There was an error retrieving results.
val code = status.errorCode
val message = status.errorMessage
}
})
Conclusion
That's it! As you can probably see, HMS Site Kit makes it pretty easy to implement location searching in your app.
This is only for devices that come with HMS pre-installed, but it's certainly a useful set of tools. Be sure to check out Huawei's full documentation for more details.
{
"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"
}
Analytics
Analytics is a technique which is widely used in businesses today. It is essential for almost all the organizations to track the progress, user interactions and plan for the measures.
There are plenty of services available in the market which provides the access to assess the data and provide analysis report. I came across such service and tried to work on it. The results of the service are quite stable and quick.
I am talking about Huawei Analytics Kit.
Huawei Analytics kit offers data focused on intelligent decision making.
Huawei Analytics kit provides fast integration and convenient data collection.
Advantages
Simple and quick integration
Secure data-related services
Fast and convenient data collection
Real-time, comprehensive and intelligent analysis
Comprehensive user profiles
Assistance in effectively reaching users.
Push Service
Push services are widely used message sending/broadcasting service.
Which has many different ways and events to apply for different requirements.
Huawei Push kit offers different kind of message sending options as
Push Notification
Data Messages
We will be focusing on the Data Message in this article.
Data Messages
Data messages are data payloads which are handled at the device.
You can customize them in the form of key-value pair.
Use Case
This article will focus on the integration of Analytics and Push kit in the Task Scheduler application and will be showcasing how this can be helpful for the user to improve their businesses.
Development Overview
Prerequisite
1. Must have a Huawei Developer Account
2. Must have Android Studio 3.0 or later
3. Must have a Huawei phone with HMS Core 5.0.2.300 or later
4. EMUI 9.1.0 or later
Software Requirements
1. Java SDK 1.7 or later
2. Android 5.0 or later
Preparation
1. Create an app or project in the Huawei App Gallery Connect.
2. Provide the SHA Key and App Package name of the project in App Information Section and enable the Analytics and Push Kit API.
3. Download the agconnect-services.json file.
4. Create an Android project.
Integration
1. Add below to build.gradle (project) file, under buildscript/repositories and allprojects/repositories.
Maven {url 'http://developer.huawei.com/repo/'}
2. Add below to build.gradle (app) file, under dependencies to use the Analytics and Push kit SDK.
Code:
dependencies{
// Import the Push SDK.
implementation 'com.huawei.hms:push:5.1.1.301'
Import the Analytics, please add Analytics SDK
implementation 'com.huawei.hms:hianalytics:5.2.0.300'
}
Tip: Minimum android version supported for these kits is 19.
3. Add below permissions to manifest file.
XML:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name= "android.permission.ACCESS_NETWORK_STATE"/>
Development Process
Push Kit
Adding below for receiving the Data Messages:
Below needs to be added to AndroidManifest file under <Application>:
Code:
<service
android:name=".DemoHmsMessageService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT"/>
</intent-filter>
</service>
Obtaining Token
Push token is important to identify the application on a device and works a unique identifier.
Client calls the getToken method in HmsInstanceId to obtain the token from the push kit server which is further used by the server to send the Push notifications to the application.
I have created a method to my Main Activity for obtaining the token as below.
Code:
private void getToken() {
// Create a thread.
new Thread() {
@Override
public void run() {
try {
// Obtain the app ID from the agconnect-service.json file.
String appId = AGConnectServicesConfig.fromContext(MainActivity.this).getString("client/app_id");
// Set tokenScope to HCM.
String tokenScope = "HCM";
String token = HmsInstanceId.getInstance(MainActivity.this).getToken(appId, tokenScope);
Log.i(TAG, "get token: " + token);
// Check whether the token is empty.
if(!TextUtils.isEmpty(token)) {
sendRegTokenToServer(token);
}
} catch (ApiException e) {
Log.e(TAG, "get token failed, " + e);
}
}
}.start();
}
// For logs
private void sendRegTokenToServer(String token) {
Log.i(TAG, "sending token to server. token:" + token);
}
Receiving Data Messages
To receive the data messages, we need to create a service and override the “onMessageReceived” method as below
Code:
import android.util.Log;
import com.huawei.hms.push.RemoteMessage;
import com.huawei.hms.push.HmsMessageService;
public class DemoHmsMessageService extends HmsMessageService {
private static final String TAG = null;
@Override
public void onMessageReceived(RemoteMessage message) {
Log.i(TAG, "onMessageReceived is called");
// Check whether the message is empty.
if (message == null) {
Log.e(TAG, "Received message entity is null!");
return;
}
// Obtain the message content.
Log.i(TAG, "get Data: " + message.getData()
+ "\n getFrom: " + message.getFrom()
+ "\n getTo: " + message.getTo()
+ "\n getMessageId: " + message.getMessageId()
+ "\n getSendTime: " + message.getSentTime()
+ "\n getDataMap: " + message.getDataOfMap()
+ "\n getMessageType: " + message.getMessageType()
+ "\n getTtl: " + message.getTtl()
+ "\n getToken: " + message.getToken());
Boolean judgeWhetherIn10s = false;
// If the message is not processed within 10 seconds, create a job to process it.
if (judgeWhetherIn10s) {
startWorkManagerJob(message);
} else {
// Process the message within 10 seconds.
processWithin10s(message);
}
}
private void startWorkManagerJob(RemoteMessage message) {
Log.d(TAG, "Start new job processing.");
}
private void processWithin10s(RemoteMessage message) {
Log.d(TAG, "Processing now.");
}}
Let’s send a Data Message to our App Users
In order to send the push notifications to the app users, we need to login to AGC.
Step 1: Choose your project
Step 2:
Goto > Grow > Push Kit
Note: Select data processing location if see the prompt to add one.
Step 3:
Goto > Notifications > Add Notifications
Step 4:
Create a new notification which needs to send to user and fill the below information.
Step 5:
Select Data message and fill the Name and Key-Value pair information.
Step 6:
Click on Test Effect and enter the token.
Click OK.
Step 7:
Scroll down and fill information for Push Scope as Specified device.
Fill Token for the device (Same as above).
Step 8:
Scroll down and fill information for Push time and other parameters.
Click Submit.
Step 9:
Click OK.
Analytics Kit
Initialization
We will be creating instance for the analytics to get the data on the console.
Code:
private Analystics(Context context){
this.context=context;
HiAnalyticsTools.enableLog();
instance = HiAnalytics.getInstance(context);
}
public static Analystics getInstance(Context context){
if (analystics==null){
analystics=new Analystics(context);
}
return analystics;
}
Checking the Analytical data on AGC
Login to AGC
Step 1: Choose your project
Step 2:
Goto Huawei Analytics
Results
Goto >> Behaviour analysis >> Page analysis
Tips and Tricks
1. Events and attributes can be customized in Analytics kit.
2. Huawei Push Kit can work well with your own push server. So to create one, refer this.
3. Always integrate the latest version of the SDK.
4. Reports can be exported from AGC.
Conclusion
This article focuses on explaining the usage of Huawei Push kit-data message, with the help of Task Scheduler application and also showcase the analytical data to analyze the user behavior on the application which will further help to improve the user interaction.
References
GitHub
Push Kit
Analytics Kit
Orignal Source
can i send custom push message?
Can we send notification on specified time?
Introduction
HarmonyOS is a future-proof distributed operating system oriented to all-scenario smart lifestyles. For consumers, HarmonyOS integrates their various smart devices into "One Super Device" that delivers the best possible interaction experience through ultra-fast connection, multi-device collaboration, and resource sharing between different devices.
{
"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"
}
Users are using two or more devices to experience an all-scenario, multi-device lifestyle. Each type of device has its unique advantages and disadvantages specific to scenarios. For example, a watch provides straightforward access to information. A TV is excellent at providing immersive watching experience, but is terrible at text input. If multiple devices can sense each other and be integrated into "One Super Device" in a distributed operating system, each device can play its strengths and avoid its weaknesses, providing users with natural and frictionless distributed experiences. In HarmonyOS, distributed experience is called distributed hop.
Distributed Scheduler
In HarmonyOS, the Distributed Scheduler provides unified component management for the "super virtual device" built by multiple devices running HarmonyOS. The Distributed Scheduler defines a unified capability baseline, API format, data structure, and service description language for applications to adapt to different hardware. You can perform various distributed tasks using the Distributed Scheduler, such as remote startup, remote calling, and seamless migration of abilities.
The Distributed Scheduler allows Ability instances (basic components for distributed scheduling) to be started, stopped, connected, disconnected, and migrated across devices, enabling cross-device component management.
Starting or stopping an ability
The Distributed Scheduler provides remote ability management. You can start an FA (Feature Ability, that is, an ability using the Page template) and start or stop a PA (Particle Ability, that is, an ability using either the Service or Data template) from a remote device.
Connecting to or disconnecting from an ability
The Distributed Scheduler provides cross-device PA control. By connecting to a remote PA, you can obtain the cross-device client for task scheduling. After the cross-device task is completed, you can then disconnect from the remote PA to unregister this client.
Migrating an ability
The Distributed Scheduler enables cross-device ability migration. You can call a migration method to seamlessly migrate an FA from the local device to a specified remote device.
When to Use Distributed Scheduler?
With the help of APIs provided by the Distributed Scheduler, you can integrate the distributed scheduling capabilities into your application to implement cross-device collaboration. Based on different ability templates and intentions, the Distributed Scheduler allows you to start a remote FA or PA, stop a remote PA, connect to a remote PA, disconnect from a remote PA, or migrate an FA to another device. The following uses device A (local device) and device B (remote device) as an example to describe when and how to use these distributed scheduling capabilities:
Device A starts an FA on device B.
On device A, the user touches the startup button provided by the local application to start a particular FA on device B. For example, to enable your users to open the photo gallery application installed on device B, all you need is to specify the action of opening the gallery application in the Intent.
How to Develop
1. Create a new Java HarmonyOS project.
2. Add the cross-device collaboration permission to the reqPermissions attribute in the config.json file for the particular ability to enable distributed scheduling.
Code:
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE"
},
{
"name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO"
},
{
"name": "ohos.permission.DISTRIBUTED_DATA"
},
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
]
3. Add device types where the application runs on config.json file
Code:
"deviceType": [
"phone",
"wearable"
]
4. Create the main layout, this application is very simple, it is just an AbilitySlice that contains a button, from where we will launch the Feature Ability (FA) from device A to device B and vice versa.
XML:
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:alignment="center"
ohos:orientation="vertical">
<Button
ohos:id="$+id:select_device_button"
ohos:height="45vp"
ohos:width="match_parent"
ohos:background_element="$graphic:button_background"
ohos:layout_alignment="horizontal_center"
ohos:margin="18vp"
ohos:text="Selecciona un dispositivo"
ohos:text_size="$float:button_text_size"/>
</DirectionalLayout>
Using multi-device preview:
5. Explicitly declare the required permission
In the onStart method of the MainAbility.java we explicitly request the permissions to the user.
Code:
<pre style="background-color: rgb(255,255,255);font-family: JetBrains Mono , monospace;font-size: 9.8pt;">@Override
public void onStart(Intent intent) {
// Explicitly request user permissions
requestPermissionsFromUser(new String[]{SystemPermission.DISTRIBUTED_DATASYNC}, 0);
super.onStart(intent);
super.setMainRoute(MainAbilitySlice.class.getName());
}</pre>
6. Obtain a list of available devices.
Add a listener to the Button select_device_button to obtain all the remote devices information:
Code:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
devicesButton = (Button) findComponentById(ResourceTable.Id_select_device_button);
devicesButton.setClickedListener(button -> {
showDevicesList();
});
}
To obtain information about all remote devices on the distributed network, call DeviceManager#getDeviceList(int). To obtain information about a specified remote device, call DeviceManager#getDeviceInfo(String).
Indicates the flag used for querying specified devices. The value DeviceInfo#FLAG_GET_ALL_DEVICE means to query all online and offline devices on the distributed network; DeviceInfo#FLAG_GET_ONLINE_DEVICE means to query all online devices on the distributed network; and DeviceInfo#FLAG_GET_OFFLINE_DEVICE means to query all offline devices on the distributed network.
Code:
List<DeviceInfo> deviceList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
if (deviceList == null || deviceList.isEmpty()) {
new ToastDialog(getContext()).setContentText("Device not found").show();
}
7. After obtain all devices list, print it on a ListDialog as follows.
Code:
private void showDevicesList() {
List<DeviceInfo> deviceList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);
if (deviceList == null || deviceList.isEmpty()) {
new ToastDialog(getContext()).setContentText("Device not found").show();
}
String[] deviceNameList = new String[deviceList.size()];
int pos = 0;
for (DeviceInfo deviceInfo : deviceList) {
deviceNameList[pos] = deviceInfo.getDeviceName();
pos++;
}
ListDialog listDialog = new ListDialog(getContext());
listDialog.setItems(deviceNameList);
listDialog.setOnSingleSelectListener((iDialog, i) -> {
DeviceInfo deviceInfo = deviceList.get(i);
if (deviceInfo == null || TextTool.isNullOrEmpty(deviceInfo.getDeviceId())) {
//Dispositivo invalido
return;
}
onRemoteDeviceSelected(deviceInfo);
});
listDialog.setTransparent(true);
listDialog.setAutoClosable(true);
listDialog.setAlignment(LayoutAlignment.CENTER);
listDialog.show();
}
8. Set a function on the click listener for the item selected for starting a remote FA and implement the remote FA startup capability.
Code:
private void onRemoteDeviceSelected(DeviceInfo deviceInfo) {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withDeviceId(deviceInfo.getDeviceId())
.withBundleName(getBundleName())
.withAbilityName(MainAbility.class)
.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)
.build();
intent.setOperation(operation);
try {
List<AbilityInfo> abilityInfoList = getBundleManager().queryAbilityByIntent(intent, 0, 0);
if (abilityInfoList != null && !abilityInfoList.isEmpty()) {
startAbility(intent);
}
} catch (RemoteException e) {
// Excepción
}
}
Use the OperationBuilder class of Intentto construct an Operation object and set the deviceId (left empty if a local ability is required), bundleName, and abilityName attributes for the object.
FLAG_ABILITYSLICE_MULTI_DEVICE Supports multi-device startup in the distributed scheduling system.
You can find the complete code:
https://github.com/jordanrsas/TalentLandProject
Tips and Tricks
All the application resource files, such as strings, images, and audio files, are stored in the resources directory, allowing you to easily access, use, and maintain them. The resources directory consists of two types of sub-directories: the base sub-directory and qualifiers sub-directories, and the rawfile sub-directory.
The name of a qualifiers sub-directory consists of one or more qualifiers that represent the application scenarios or device characteristics, covering the mobile country code (MCC), mobile network code (MNC), language, script, country or region, screen orientation, device type, night mode, and screen density. The qualifiers are separated using underscores (_) or hyphens (-). When creating a qualifiers sub-directory, you need to understand the directory naming conventions and the rules for matching qualifiers sub-directories and the device status.
Device type
Indicates the device type. The value can be:
phone: smartphones
tablet: tablets
car: head units
tv: smart TVs
wearable: wearables
Then, to configure different font sizes to fit the different devices that we have set up in the project, we create the wearable directory within resources directory, where we will have an element directory that contains the float.json file where we will add the different measurements depending on of the device.
References
Distributed Scheduler Overview
https://developer.harmonyos.com/en/docs/documentation/doc-guides/ability-distributed-overview-0000001050419345
DeviceManager
https://developer.harmonyos.com/en/docs/documentation/doc-references/devicemanager-0000001054358820
ListDialog
https://developer.harmonyos.com/en/docs/documentation/doc-references/listdialog-0000001054120087
Resource File Categories
https://developer.harmonyos.com/en/docs/documentation/doc-guides/basic-resource-file-categories-0000001052066099
Intent
https://developer.harmonyos.com/en/docs/documentation/doc-references/intent-0000001054120019
Intent.OperationBuilder
https://developer.harmonyos.com/en/docs/documentation/doc-references/intent_operationbuilder-0000001054119948
Original Source
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 - PrototypingIf 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 requirementsMany 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 knowledgeIf 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 SetupStarting 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!
RegistrationAs 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 userListIn 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 ManagerWith 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 - PrototypingIf 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 - PrototypingIf 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 requirementsMany 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 knowledgeIf 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 requirementsMany 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 knowledgeIf 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 RegisterToday 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 ActivityWithin 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 ActivityFor 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.