[Huawei Developers] Freemind R:
Sometimes you may need to show an alert to an specific user, for example, as a result of the interaction of another user (someone else commented your post), or to notify an event caused by your own business logic (Sign In detected in another device). In this article I'm going to show you how to bind a push token to a user id, so you can send personalized push notifications.

Previous requirements

A verified developer account

An app project with the HMS core SDK

Enabling the requred services

Go to your project in AppGallery Connect and make sure to enable Push Kit and Account Kit on the Manage APIs tab.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/002/383/434/2640052000002383434.20200925222203.83562236858322838860853521176876:50511014010330:2800:878D29C46AC33F8493A7D60AF1E290CC8A7D02997389C6AD994B54BF7767A9F3.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/002/383/434/2640052000002383434.20200925222203.83562236858322838860853521176876:50511014010330:2800:878D29C46AC33F8493A7D60AF1E290CC8A7D02997389C6AD994B54BF7767A9F3.png[/IMG]

Now go to Push Kit on the left side panel to properly enabling the Push Service.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/002/383/434/2640052000002383434.20200925222756.00670425177315402547119243176792:50511014010330:2800:5D8B9F31AA833A59BA8971B62E037D8259A30273B583ACF7998126547D58806B.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/002/383/434/2640052000002383434.20200925222756.00670425177315402547119243176792:50511014010330:2800:5D8B9F31AA833A59BA8971B62E037D8259A30273B583ACF7998126547D58806B.png[/IMG]

Integrting the SDKs

First, you must add the related dependencies for Account kit and Push kit. Add the next lines under dependencies of your app level build.gradle.

implementation 'com.huawei.hms:hwid:5.0.1.301'
implementation 'com.huawei.hms:push:5.0.1.300'

Note: please refer to the related document to obtain the most recent version of each SDK

Solution proposal

We want to bind a push token to the user's email (you can choose other user ID in your own implementation). We know we can get both in our app, but we don't know exaclty when. So, my suggestion is registering a BroadcastReceiver upon the app startup, which will wait for the token and the email. When the receiver has both, it will perform a POST operation to our server to save/update the binding. Once the operation is complete, the Receiver will perform a self unregister operation.

class MyBroadcastReceiver: BroadcastReceiver() {

companion object {
private val TAG="Receiver"
val PARAM="param"
val MAIL="mail"
val TOKEN="token"
val ACTION="ACTION_REGISTER_USER"
private val SERVER="https://a8a7ycwh8b.execute-api.us-east-2.amazonaws.com/Prod"
}

private var email:String?=null
private var token:String?=null
private var context:Context?=null

override fun onReceive(context: Context?, intent: Intent?) {
this.context=context
intent?.extras?.let {
val key=it.getString(PARAM)
val value=it.getString(key)
checkParamValues(key!!,value!!)
}
}

fun register(context:Context){
Log.d(TAG,"Registering Receiver")
context.registerReceiver(this,IntentFilter(ACTION))
}

private fun unregister(){
Log.d(TAG,"UnregisteringReceiver")
context?.unregisterReceiver(this)
}

private fun checkParamValues(key: String, value: String) {
Log.e(TAG,"Received $key")
when(key){
MAIL -> {
email=value
token?.let{registerInServer(value,it)}
}
TOKEN -> {
token=value
email?.let { registerInServer(it,value) }
}
}
}

private fun registerInServer(mail:String,token:String){
//perform registerOperation
CoroutineScope(Dispatchers.IO).launch {
val connection= URL(SERVER).openConnection() as HttpURLConnection
val outputStream=connection.apply{
requestMethod="POST"
doInput=true
doOutput=true
}.outputStream
val jsonObject=JSONObject().apply {
put(MAIL,mail)
put(TOKEN,token)
}
outputStream.write(jsonObject.toString().toByteArray())
Log.d("Response","${connection.responseCode} ${connection.responseMessage}")
//unregisterReceiver
unregister()
}

}
}

To ensure the receiver will be registered before we get the push token, we can define our own Application class and register the Receiver from the onCreate method. If we register a BroadcastReceiver by using the ApplicationContext, it will be alive as long as the app is running, that's very convenient for our purpose.

class MyApplication: Application() {
override fun onCreate() {
super.onCreate()
MyBroadcastReceiver().register(this)
}
}

Note: Don't forget to add your custom application class to your AndroidManifest.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hms.demo.accountpush">

<application
android:name=".MyApplication"
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/AppTheme">

If you already know how to implement Account kit and Push kit, maybe your problem is solved with this example. If not, please keep reading.

Integrating Push Kit

Create a class which inherits from HmsMessageService and override the methoods onNewToken and onMessageReceived.

class MyHmsMessageService: HmsMessageService() {
override fun onNewToken(token: String?) {
super.onNewToken(token)
token?.let {
Log.d("newToken",it)
publishToken(it)
}
}

private fun publishToken(token:String) {
val intent= Intent().apply {
action = MyBroadcastReceiver.ACTION
putExtra(MyBroadcastReceiver.PARAM,MyBroadcastReceiver.TOKEN)
putExtra(MyBroadcastReceiver.TOKEN,token)
}
sendBroadcast(intent)
}

override fun onMessageReceived(message: RemoteMessage?) {
message?.let {
Log.i("OnNewMessage",it.data)
val map=it.dataOfMap
for( key in map.keys){
Log.d("onNewMessage",map[key]!!)
}
}
}
}

Once the push token is delivered, we must report it to our BroadcastReceiver by using the publishToken funcion.

Add the next settings to your Android Manifest under <application>

<!--Enables the push automatic initialization-->
<meta-data
android:name="push_kit_auto_init_enabled"
android:value="true"/>

<!--Registers the custom HmsMessageService-->
<service
android:name=".MyHmsMessageService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT"/>
</intent-filter>
</service>

This configuration will enable the automatic initialization, so the push token will be always delivered to the onNewToken method automatically.

Integrating Account Kit

First, we will ask if the app has been authorized previously. For this we can use the Silent Sign In API.

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//We will try to use the silent sign in
val authParams = HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setScopeList(LoginActivity.SCOPES)
.createParams()
val service = HuaweiIdAuthManager.getService(this@MainActivity, authParams)
val task = service.silentSignIn()

task.addOnSuccessListener {//The sign in is successful, we can jump to the profile activity
//Apply for a token and register in the server side
val intent=Intent(this@MainActivity,ProfileActivity::class.java)
intent.putExtra(ProfileActivity.ACCOUNT,it)
startActivity(intent)
}

task.addOnFailureListener{//Redirect to the login activity
startActivity(Intent(this@MainActivity,LoginActivity::class.java))
finish()
}
}

}

If a user has authorized our app before, he will be logged in automatically. By other way he will be redirected to our login activity.

Create the layout of your login activity by using the Huawei Sign In button, provided on the Account kit SDK. This app will support Auth Code and ID Token.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity">

<com.huawei.hms.support.hwid.ui.HuaweiIdAuthButton
android:id="@+id/code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />

<com.huawei.hms.support.hwid.ui.HuaweiIdAuthButton
android:id="@+id/idtoken"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
app:hwid_color_policy="hwid_color_policy_blue"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="Authorization Code"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:text="Id Token"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/code" />

</androidx.constraintlayout.widget.ConstraintLayout>

Let's define the logic of the LoginActivity, as we want to get the user's email, we must apply for it by using the apropiated Scope. The user must approve the email permission manually at the authorization page, if not, the sign in response wont include it. In this case we are displayung a message to the user, asking for the email approval.

class LoginActivity : AppCompatActivity(), View.OnClickListener {

companion object {
private val AUTH_CODE = 100
private val AUTH_TOKEN = 200
private val TAG = "LoginActivity"
val SCOPES = listOf(Scope("email"))
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
code.setOnClickListener(this)
idtoken.setOnClickListener(this)
}

override fun onClick(v: View?) {
when (v?.id) {
R.id.code -> signInCode()

R.id.idtoken -> signInToken()
else -> {
}
}
}

private fun signInCode() {
val authParams = HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setAuthorizationCode()
.setScopeList(SCOPES)
.createParams()
val service = HuaweiIdAuthManager.getService(this, authParams)
startActivityForResult(service.getSignInIntent(), AUTH_CODE)
}

private fun signInToken() {
val authParams = HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setIdToken()
.setScopeList(SCOPES)
.createParams()
val service = HuaweiIdAuthManager.getService(this, authParams)
startActivityForResult(service.getSignInIntent(), AUTH_TOKEN)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
AUTH_CODE -> handleAuthCodeResult(data)

AUTH_TOKEN -> handleAuthTokenResult(data)
}
}

private fun handleAuthTokenResult(data: Intent?) {
val authHuaweiIdTask = HuaweiIdAuthManager.parseAuthResultFromIntent(data)
if (authHuaweiIdTask.isSuccessful) {
// The sign-in is successful, and the user's HUAWEI ID information and ID token are obtained.
val huaweiAccount = authHuaweiIdTask.result
checkAccount(huaweiAccount)
} else {
// The sign-in failed. No processing is required. Logs are recorded to facilitate fault locating.
Log.e(
TAG,
"sign in failed : " + (authHuaweiIdTask.exception as ApiException).message
)
}
}

private fun handleAuthCodeResult(data: Intent?) {

val authHuaweiIdTask = HuaweiIdAuthManager.parseAuthResultFromIntent(data)
if (authHuaweiIdTask.isSuccessful) {
//The sign-in is successful, and the user's HUAWEI ID information and authorization code are obtained.
val huaweiAccount = authHuaweiIdTask.result
checkAccount(huaweiAccount)
} else {
// The sign-in failed.
Log.e(TAG, "sign in failed : " + (authHuaweiIdTask.exception as ApiException).message)
}
}

private fun jump(huaweiAccount: AuthHuaweiId) {
val intent = Intent(this, ProfileActivity::class.java)
intent.putExtra("account", huaweiAccount)
startActivity(intent)
finish()
}

private fun checkAccount(huaweiAccount: AuthHuaweiId) {
if (huaweiAccount.email.isNullOrEmpty()) {
Toast.makeText(
this, "Please allow email access in the authorization page", Toast.LENGTH_LONG
).show()
return
}
jump(huaweiAccount)
}

}

Now, from the profile activity (or the activity with the core functionality of your app) we must report the user's email to our Receiver.

companion object{
private val TAG="ProfileActivity"
val ACCOUNT="account"
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_profile)
val huaweiId=intent.extras?.getParcelable<AuthHuaweiId>(ACCOUNT)
huaweiId?.apply {
name.text=displayName
mail.text=email
publishMail(email)
loadProfilePic(avatarUriString)
}
}

Bonus: Server side configuration

The BroadcastReceiver in this example is targeting a Lambda function on AWS, this is the code which makes the data insertion in a DynamoDB table.

'use strict'

const AWS = require('aws-sdk');
AWS.config.update({ region: "us-east-2" });

exports.handler = async (event) => {
// TODO implement
console.log(event);
const ddb = new AWS.DynamoDB({ apiVersion: "2012-08-10" });
const documentClient = new AWS.DynamoDB.DocumentClient({ region: "us-east-2" });

const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
const result=await writeDB(event,documentClient).catch(function(){
response.statusCode=400;
});
return response;
};

function writeDB(event, documentClient){
const entry = {
TableName: "PushBinding",
Item: {
mail: event.mail,
token: event.token
}
};
console.log(JSON.stringify(entry));
return documentClient.put(entry).promise();

}

Conclusion

Now you know how to associate a user id with a push token to send personalized push notifications. The Broadcast Receiver is used here to avoid stopping the app navigation by waiting for the push token or the user's email.