[Huawei Developers] HMS Community:
Introduction

In this article, we can learn how to send push notifications using this Huawei Push Kit to the device in the Patient Tracking app. Users can add patient details in this app, so the data will be saved in the room database, it can be accessed offline also. User can easily track their patient list who are visited the hospital. In this app, users can add, update, delete and fetch operations.

So, I will provide a series of articles on this Patient Tracking App, in upcoming articles I will integrate other Huawei Kits.

If you are new to this application, follow my previous articles.

https://forums.developer.huawei.com/forumPortal/en/topic/0201902220661040078

Push Kit

Huawei Push Kit is a messaging service developed by Huawei for developers to send messages to apps on users’ devices in real-time. Push Kit supports two types of messages: notification messages and data messages. You can send notifications and data messages to your users from your server using the Push Kit APIs or directly from the AppGallery Push Kit Console.

AppGallery Connect

Find the Push Kit message service in AppGallery connect dashboard.

Choose My Projects > Grow > Push Kit, and click Enable now.

Follow the steps to send the notification message to device from AppGallery Connect, Sending a Notification Message.

Requirements

1. Any operating system (MacOS, Linux and Windows).

2. Must have a Huawei phone with HMS 4.0.0.300 or later.

3. Must have a laptop or desktop with Android Studio, Jdk 1.8, SDK platform 26 and Gradle 4.6 and above installed.

4. Minimum API Level 24 is required.

5. Required EMUI 9.0.0 and later version devices.

How to integrate HMS Dependencies

1. First register as Huawei developer and complete identity verification in Huawei developers website, refer to register a Huawei ID.

2. Create a project in android studio, refer Creating an Android Studio Project.

3. Generate a SHA-256 certificate fingerprint.

4. To generate SHA-256 certificate fingerprint. On right-upper corner of android project click Gradle, choose Project Name > Tasks > android, and then click signingReport, as follows.

Note: Project Name depends on the user created name.

5. Create an App in AppGallery Connect.

6. Download the agconnect-services.json file from App information, copy and paste in android Project under app directory, as follows.

7. Enter SHA-256 certificate fingerprint and click Save button, as follows.

Note: Above steps from Step 1 to 7 is common for all Huawei Kits.

8. Click Manage APIs tab and enable Push Kit.

9. Add the below maven URL in build.gradle(Project) file under the repositories of buildscript, dependencies and allprojects, refer Add Configuration.

maven { url 'http://developer.huawei.com/repo/' }
classpath 'com.huawei.agconnect:agcp:1.6.0.300'

10. Add the below plugin and dependencies in build.gradle(Module) file.

apply plugin: id 'com.huawei.agconnect'
apply plugin: id 'kotlin-kapt'
// Huawei AGC
implementation 'com.huawei.agconnect:agconnect-core:1.6.0.300'
// Room Database
implementation "androidx.room:room-runtime:2.4.2"
kapt "androidx.room:room-compiler:2.4.2"
implementation "androidx.room:room-ktx:2.4.2"
androidTestImplementation "androidx.room:room-testing:2.4.2"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
// Recyclerview
implementation 'androidx.recyclerview:recyclerview:1.2.1'

11. Now Sync the gradle.

12. Add the required permission to the AndroidManifest.xml file.

// Push Kit
<uses-permission android:name="android.permission.INTERNET" />
<service
android:name=".PushService"
android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT" />
</intent-filter>
</service>

Let us move to development

I have created a project on Android studio with empty activity let us start coding.

In the MainActivity.kt to find the get token method for Push service.

class MainActivity : AppCompatActivity() {

getToken()

}

private fun getToken() {
showLog("getToken:begin")
object : Thread() {
override fun run() {
try {
// read from agconnect-services.json
val appId = "106429807"
val token = HmsInstanceId.getInstance(this@MainActivity).getToken(appId, "HCM")
Log.i(TAG, "get token:$token")
if (!TextUtils.isEmpty(token)) {
sendRegTokenToServer(token)
}
showLog("get token:$token")
} catch (e: ApiException) {
Log.e(TAG, "get token failed, $e")
showLog("get token failed, $e")
}
}
}.start()
}

fun showLog(log: String?) {
runOnUiThread {
val tvView = findViewById<View?>(R.id.tv_log)
val svView = findViewById<View?>(R.id.sv_log)
if (tvView is TextView) {
tvView.text = log
}
if (svView is ScrollView) {
svView.fullScroll(View.FOCUS_DOWN)
}
}
}

private fun sendRegTokenToServer(token: String?) {
Log.i(TAG, "sending token to server. token:$token")
}

companion object {
private const val TAG: String = "PushDemoLog"
private const val CODELABS_ACTION: String = "com.huawei.codelabpush.action"
}

}

Create PushService.kt class to send the push notification to device.

class PushService : HmsMessageService() {

// When an app calls the getToken method to apply for a token from the server,
// if the server does not return the token during current method calling, the server can return the token through this method later.
// This method callback must be completed in 10 seconds. Otherwise, you need to start a new Job for callback processing.
// @param token token
override fun onNewToken(token: String?) {
Log.i(TAG, "received refresh token:$token")
// send the token to your app server.
if (!token.isNullOrEmpty()) {
// This method callback must be completed in 10 seconds. Otherwise, you need to start a new Job for callback processing.
refreshedTokenToServer(token)
}
val intent = Intent()
intent.action = CODELABS_ACTION
intent.putExtra("method", "onNewToken")
intent.putExtra("msg", "onNewToken called, token: $token")
sendBroadcast(intent)
}

private fun refreshedTokenToServer(token: String) {
Log.i(TAG, "sending token to server. token:$token")
}

// This method is used to receive downstream data messages.
// This method callback must be completed in 10 seconds. Otherwise, you need to start a new Job for callback processing.
// @param message RemoteMessage
override fun onMessageReceived(message: RemoteMessage?) {
Log.i(TAG, "onMessageReceived is called")
if (message == null) {
Log.e(TAG, "Received message entity is null!")
return
}
// getCollapseKey() Obtains the classification identifier (collapse key) of a message.
// getData() Obtains valid content data of a message.
// getMessageId() Obtains the ID of a message.
// getMessageType() Obtains the type of a message.
// getNotification() Obtains the notification data instance from a message.
// getOriginalUrgency() Obtains the original priority of a message.
// getSentTime() Obtains the time when a message is sent from the server.
// getTo() Obtains the recipient of a message.
Log.i(TAG, """getCollapseKey: ${message.collapseKey}
getData: ${message.data}
getFrom: ${message.from}
getTo: ${message.to}
getMessageId: ${message.messageId}
getMessageType: ${message.messageType}
getSendTime: ${message.sentTime}
getTtl: ${message.ttl}
getSendMode: ${message.sendMode}
getReceiptMode: ${message.receiptMode}
getOriginalUrgency: ${message.originalUrgency}
getUrgency: ${message.urgency}
getToken: ${message.token}""".trimIndent())
// getBody() Obtains the displayed content of a message
// getTitle() Obtains the title of a message
// getTitleLocalizationKey() Obtains the key of the displayed title of a notification message
// getTitleLocalizationArgs() Obtains variable parameters of the displayed title of a message
// getBodyLocalizationKey() Obtains the key of the displayed content of a message
// getBodyLocalizationArgs() Obtains variable parameters of the displayed content of a message
// getIcon() Obtains icons from a message
// getSound() Obtains the sound from a message
// getTag() Obtains the tag from a message for message overwriting
// getColor() Obtains the colors of icons in a message
// getClickAction() Obtains actions triggered by message tapping
// getChannelId() Obtains IDs of channels that support the display of messages
// getImageUrl() Obtains the image URL from a message
// getLink() Obtains the URL to be accessed from a message
// getNotifyId() Obtains the unique ID of a message
val notification = message.notification
if (notification != null) {
Log.i(TAG, """
getTitle: ${notification.title}
getTitleLocalizationKey: ${notification.titleLocalizationKey}
getTitleLocalizationArgs: ${Arrays.toString(notification.titleLocalizationArgs)}
getBody: ${notification.body}
getBodyLocalizationKey: ${notification.bodyLocalizationKey}
getBodyLocalizationArgs: ${Arrays.toString(notification.bodyLocalizationArgs)}
getIcon: ${notification.icon}
getImageUrl: ${notification.imageUrl}
getSound: ${notification.sound}
getTag: ${notification.tag}
getColor: ${notification.color}
getClickAction: ${notification.clickAction}
getIntentUri: ${notification.intentUri}
getChannelId: ${notification.channelId}
getLink: ${notification.link}
getNotifyId: ${notification.notifyId}
isDefaultLight: ${notification.isDefaultLight}
isDefaultSound: ${notification.isDefaultSound}
isDefaultVibrate: ${notification.isDefaultVibrate}
getWhen: ${notification.`when`}
getLightSettings: ${Arrays.toString(notification.lightSettings)}
isLocalOnly: ${notification.isLocalOnly}
getBadgeNumber: ${notification.badgeNumber}
isAutoCancel: ${notification.isAutoCancel}
getImportance: ${notification.importance}
getTicker: ${notification.ticker}
getVibrateConfig: ${notification.vibrateConfig}
getVisibility: ${notification.visibility}""".trimIndent())
showNotification(notification.title,notification.body)
}
val intent = Intent()
intent.action = CODELABS_ACTION
intent.putExtra("method", "onMessageReceived")
intent.putExtra("msg", "onMessageReceived called, message id:" + message.messageId + ", payload data:" + message.data)
sendBroadcast(intent)
val judgeWhetherIn10s = false
// If the messages are not processed in 10 seconds, the app needs to use WorkManager for processing.
if (judgeWhetherIn10s) {
startWorkManagerJob(message)
} else {
// Process message within 10s
processWithin10s(message)
}
}

private fun showNotification(title: String?, body: String?) {
val intent = Intent(this, MainActivity::class.java)
// intent.putExtra("URL", "https://document.desiringgod.org/the-scars-that-have-shaped-me-en.pdf")
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT)
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.sym_def_app_icon)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setSound(soundUri)
.setContentIntent(pendingIntent)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(0, notificationBuilder.build())
}

private fun startWorkManagerJob(message: RemoteMessage?) {
Log.d(TAG, "Start new Job processing.")
}

private fun processWithin10s(message: RemoteMessage?) {
Log.d(TAG, "Processing now.")
}

override fun onMessageSent(msgId: String?) {
Log.i(TAG, "onMessageSent called, Message id:$msgId")
val intent = Intent()
intent.action = CODELABS_ACTION
intent.putExtra("method", "onMessageSent")
intent.putExtra("msg", "onMessageSent called, Message id:$msgId")
sendBroadcast(intent)
}

override fun onSendError(msgId: String?, exception: Exception?) {
Log.i(TAG, "onSendError called, message id:$msgId, ErrCode:${(exception as SendException).errorCode}, " +
"description:${exception.message}")
val intent = Intent()
intent.action = CODELABS_ACTION
intent.putExtra("method", "onSendError")
intent.putExtra("msg", "onSendError called, message id:$msgId, ErrCode:${exception.errorCode}, " +
"description:${exception.message}")
sendBroadcast(intent)
}

override fun onTokenError(e: Exception) {
super.onTokenError(e)
}

companion object {
private const val TAG: String = "PushDemoLog"
private const val CODELABS_ACTION: String = "com.huawei.codelabpush.action"
}

}

Create a PatientRecord.kt class annotated with @entity to create a table for each class.

@Entity(tableName = "patient_records")
data class PatientRecord(
@PrimaryKey(autoGenerate = true)
val id: Long,
val name: String,
val age: String,
val gender: String,
val phoneNumber: String,
val address: String,
val disease: String
)

Create a PatientDao.kt interface class annotated with @dao and responsible for defining the methods that access the database.

@Dao
interface PatientDao {

@Query("SELECT * from patient_records")
fun getall(): LiveData<List<PatientRecord>>

@Insert(onConflict = OnConflictStrategy.ABORT)
suspend fun insert(item: PatientRecord)

@Query("SELECT * FROM patient_records WHERE patient_records.id == :id")
fun get(id: Long): LiveData<PatientRecord>

@Update
suspend fun update(vararg items: PatientRecord)

@Delete
suspend fun delete(vararg items: PatientRecord)

}

Create a AppRoomDatabase.kt abstract class that extends RoomDatabase annotated with @database to lists the entities contained in the database, and the DAOs which access them.

@Database(entities = [PatientRecord::class], version = 1)
abstract class AppRoomDatabase : RoomDatabase() {

abstract fun patientrecordDao(): PatientDao

companion object {
@Volatile
private var INSTANCE: RoomDatabase? = null

fun getDatabase(context: Context): AppRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance as AppRoomDatabase
}
synchronized(this) {
val instance = Room.databaseBuilder(context.applicationContext,AppRoomDatabase::class.java,
"patient_record_database").fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}

}

Create a Repository.kt class to find the functions.

class Repository(private val mDao: PatientDao) {

val allItems: LiveData<List<PatientRecord>> = mDao.getall()

fun get(id: Long): LiveData<PatientRecord> {
return mDao.get(id)
}
suspend fun update(item: PatientRecord) {
mDao.update(item)
}
suspend fun insert(item: PatientRecord) {
mDao.insert(item)
}
suspend fun delete(item: PatientRecord) {
mDao.delete(item)
}

}

Create a ViewModel.kt class that extends AndroidViewModel and provides the Repository functions.

class ViewModel(application: Application): AndroidViewModel(application) {

private val repository: Repository
val allItems: LiveData<List<PatientRecord>>

init {
Log.d(ContentValues.TAG, "Inside ViewModel init")
val dao = AppRoomDatabase.getDatabase(application).patientrecordDao()
repository = Repository(dao)
allItems = repository.allItems
}

fun insert(item: PatientRecord) = viewModelScope.launch {
repository.insert(item)
}

fun update(item: PatientRecord) = viewModelScope.launch {
repository.update(item)
}

fun delete(item: PatientRecord) = viewModelScope.launch {
repository.delete(item)
}

fun get(id: Long) = repository.get(id)

}

In the PatientActivity.kt activity to find the business logic for button click.

class PatientActivity : AppCompatActivity() {

private lateinit var myViewModel: ViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_patient)

val recyclerView = recyclerview_patients
val adapter = PatientAdapter(this)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)

// buttonAddPat = findViewById(R.id.btn_float)
btn_float!!.setOnClickListener(View.OnClickListener {
val intent = Intent(this, AddPatientDetails::class.java)
startActivity(intent)
})

myViewModel = ViewModelProvider(this)[ViewModel::class.java]
myViewModel.allItems.observe(this, Observer { items ->
items?.let { adapter.setItems(it) }
})

}

}

Create a PatientAdapter.kt adapter class to hold the list.

class PatientAdapter internal constructor (context: Context) : RecyclerView.Adapter<PatientAdapter.PatientRecordViewHolder>(){

private val inflater: LayoutInflater = LayoutInflater.from(context)
private var itemsList = emptyList<PatientRecord>().toMutableList()
private val onClickListener: View.OnClickListener

init {
onClickListener = View.OnClickListener { v ->
val item = v.tag as PatientRecord
Log.d(ContentValues.TAG, "Setting onClickListener for item ${item.id}")
val intent = Intent(v.context, AddPatientDetails::class.java).apply {
putExtra(PATIENT_RECORD_ID, item.id)
}
v.context.startActivity(intent)
}
}

inner class PatientRecordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val itemId: TextView = itemView.findViewById(R.id.patientrecord_viewholder_id)
val itemName: TextView = itemView.findViewById(R.id.txt_name)
val itemAge: TextView = itemView.findViewById(R.id.txt_age)
val itemGender: TextView = itemView.findViewById(R.id.txt_gender)
val itemPhone: TextView = itemView.findViewById(R.id.txt_phone)
val itemAddress: TextView = itemView.findViewById(R.id.txt_address)
val itemDisease: TextView = itemView.findViewById(R.id.txt_disease)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PatientRecordViewHolder {
val itemView = inflater.inflate(R.layout.patient_list, parent, false)
return PatientRecordViewHolder(itemView)
}

override fun onBindViewHolder(holder: PatientRecordViewHolder, position: Int) {
val current = itemsList[position]
// Needed: will be referenced in the View.OnClickListener above
holder.itemView.tag = current
with(holder) {
// Set UI values
itemId.text = current.id.toString()
// itemRecord.text = current.record
itemName.text = current.name
itemAge.text = current.age
itemGender.text = current.gender
itemPhone.text = current.phoneNumber
itemAddress.text = current.address
itemDisease.text = current.disease
// Set handlers
itemView.setOnClickListener(onClickListener)
}
}

override fun getItemCount() = itemsList.size

internal fun setItems(items: List<PatientRecord>) {
this.itemsList = items.toMutableList()
notifyDataSetChanged()
}

companion object {
const val PATIENT_RECORD_ID : String = "patientrecord_id"
// val Tag = "Punch"
}

}

In the AddPatientDetails.kt activity to find the business logic to add items.

class AddPatientDetails : AppCompatActivity() {

private lateinit var dataViewModel: ViewModel
private var recordId: Long = 0L
private var isEdit: Boolean = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_patient_details)

dataViewModel = ViewModelProvider(this)[ViewModel::class.java]
if (intent.hasExtra(PATIENT_RECORD_ID)) {
recordId = intent.getLongExtra(PATIENT_RECORD_ID, 0L)
dataViewModel.get(recordId).observe(this, Observer {
val viewId = findViewById<TextView>(R.id.patient_record_id)
val viewName = findViewById<EditText>(R.id.edt_name)
val viewAge = findViewById<EditText>(R.id.edt_age)
val viewGender = findViewById<EditText>(R.id.edt_gender)
val viewPhone = findViewById<EditText>(R.id.edt_phone)
val viewAddress = findViewById<EditText>(R.id.edt_address)
val viewDisease = findViewById<EditText>(R.id.edt_disease)

if (it != null) {
// populate with data
viewId.text = it.id.toString()
viewName.setText(it.name)
viewAge.setText(it.age)
viewGender.setText(it.gender)
viewPhone.setText(it.phoneNumber)
viewAddress.setText(it.address)
viewDisease.setText(it.disease)
}
})
isEdit = true
}

val save = btn_save
save.setOnClickListener { view ->
val id = 0L
val mName = edt_name.text.toString()
val mAge = edt_age.text.toString()
val mGender = edt_gender.text.toString()
val mPhone = edt_phone.text.toString()
val mAddress = edt_address.text.toString()
val mDisease = edt_disease.text.toString()

if (mName.isBlank() ) {
Toast.makeText(this, "Name is blank", Toast.LENGTH_SHORT).show()
}
else if (mAge.isBlank()) {
Toast.makeText(this, "Age is blank", Toast.LENGTH_SHORT).show()
}
else if(mGender.isBlank()) {
Toast.makeText(this, "Gender is blank", Toast.LENGTH_SHORT).show()
}
else if(mPhone.isBlank()) {
Toast.makeText(this, "Phone Number is blank", Toast.LENGTH_SHORT).show()
}
else if(mAddress.isBlank()) {
Toast.makeText(this, "Address is blank", Toast.LENGTH_SHORT).show()
}
else if(mDisease.isBlank()) {
Toast.makeText(this, "Disease is blank", Toast.LENGTH_SHORT).show()
}
else {
val item = PatientRecord( id = id, name = mName, age = mAge, gender = mGender, phoneNumber = mPhone,
address = mAddress, disease = mDisease)
dataViewModel.insert(item)
finish()
}
}

val update = btn_update
update.setOnClickListener { view ->
val id = patient_record_id.text.toString().toLong()
val nName = edt_name.text.toString()
val nAge = edt_age.text.toString()
val nGender = edt_gender.text.toString()
val nPhone = edt_phone.text.toString()
val nAddress = edt_address.text.toString()
val nDisease = edt_disease.text.toString()
if (nName.isBlank() && nAge.isBlank() && nPhone.isBlank()) {
Toast.makeText(this, "Empty data is not allowed", Toast.LENGTH_SHORT).show()
} else {
val item = PatientRecord(id = id, name = nName, age = nAge, gender = nGender, phoneNumber = nPhone,
address = nAddress, disease = nDisease)
dataViewModel.update(item)
finish()
}
}

val delete = btn_delete
delete.setOnClickListener {
val id = patient_record_id.text.toString().toLong()
val nName = edt_name.text.toString()
val nAge = edt_age.text.toString()
val nGender = edt_gender.text.toString()
val nPhone = edt_phone.text.toString()
val nAddress = edt_address.text.toString()
val nDisease = edt_disease.text.toString()

val item = PatientRecord(id = id, name = nName, age = nAge, gender = nGender, phoneNumber = nPhone,
address = nAddress, disease = nDisease)
dataViewModel.delete(item)
finish()
}

}

}

In the activity_patient.xml we can create the UI screen for button.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".room.PatientActivity">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview_patients"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btn_float"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:backgroundTint="@color/teal_200"
android:clickable="true"
app:borderWidth="0dp"
app:srcCompat="@android:drawable/ic_input_add"
android:focusable="true" />

</RelativeLayout>

In the activity_add_patient_details.xml we can create the UI screen for adding items.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:padding="5dp"
android:layout_marginTop="10dp"
android:orientation="vertical"
tools:context=".room.AddPatientDetails">

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginTop="32dp"
android:layout_marginRight="62dp"
android:background="@drawable/blue_border_rounded_cornwe"
tools:ignore="MissingConstraints">
<TextView
android:id="@+id/patient_record_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/username_icons"
android:background="@android:color/transparent"
android:hint="ID "
android:inputType="textEmailAddress"
android:maxLines="1"
android:paddingLeft="17dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:textSize="13sp">
</TextView>
<ImageView
android:id="@+id/username_icons"
android:layout_width="20dp"
android:layout_height="17dp"
android:layout_centerVertical="true"
android:layout_marginLeft="17dp"
android:src="@drawable/id_icon" />
</RelativeLayout>

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginTop="12dp"
android:layout_marginRight="62dp"
android:background="@drawable/blue_border_rounded_cornwe"
tools:ignore="MissingConstraints">
<EditText
android:id="@+id/edt_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/username_icon"
android:background="@android:color/transparent"
android:hint="Name "
android:inputType="textEmailAddress"
android:maxLines="1"
android:paddingLeft="17dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:textSize="13sp">
</EditText>
<ImageView
android:id="@+id/username_icon"
android:layout_width="20dp"
android:layout_height="17dp"
android:layout_centerVertical="true"
android:layout_marginLeft="17dp"
android:src="@drawable/username" />
</RelativeLayout>

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginTop="12dp"
android:layout_marginRight="62dp"
android:background="@drawable/blue_border_rounded_cornwe"
tools:ignore="MissingConstraints">
<EditText
android:id="@+id/edt_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/username_icon2"
android:background="@android:color/transparent"
android:hint="Age "
android:inputType="textEmailAddress"
android:maxLines="1"
android:paddingLeft="17dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:textSize="13sp">
</EditText>
<ImageView
android:id="@+id/username_icon2"
android:layout_width="20dp"
android:layout_height="17dp"
android:layout_centerVertical="true"
android:layout_marginLeft="17dp"
android:src="@drawable/age_icon" />
</RelativeLayout>

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginTop="12dp"
android:layout_marginRight="62dp"
android:background="@drawable/blue_border_rounded_cornwe"
tools:ignore="MissingConstraints">
<EditText
android:id="@+id/edt_gender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:layout_toRightOf="@id/username_icon3"
android:hint="Gender "
android:inputType="textEmailAddress"
android:maxLines="1"
android:paddingLeft="17dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:textSize="13sp">
</EditText>
<ImageView
android:id="@+id/username_icon3"
android:layout_width="20dp"
android:layout_height="17dp"
android:layout_centerVertical="true"
android:layout_marginLeft="17dp"
android:src="@drawable/gender_icon" />
</RelativeLayout>

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginTop="12dp"
android:layout_marginRight="62dp"
android:background="@drawable/blue_border_rounded_cornwe"
tools:ignore="MissingConstraints">
<EditText
android:id="@+id/edt_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:layout_toRightOf="@id/username_icon4"
android:hint="Phone Number "
android:inputType="textEmailAddress"
android:maxLines="1"
android:paddingLeft="17dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:textSize="13sp">
</EditText>
<ImageView
android:id="@+id/username_icon4"
android:layout_width="20dp"
android:layout_height="17dp"
android:layout_centerVertical="true"
android:layout_marginLeft="17dp"
android:src="@drawable/phonenumber_icon" />
</RelativeLayout>

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginTop="12dp"
android:layout_marginRight="62dp"
android:background="@drawable/blue_border_rounded_cornwe"
tools:ignore="MissingConstraints">
<EditText
android:id="@+id/edt_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:layout_toRightOf="@id/username_icon5"
android:hint="Address "
android:inputType="textEmailAddress"
android:maxLines="1"
android:paddingLeft="17dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:textSize="13sp">
</EditText>
<ImageView
android:id="@+id/username_icon5"
android:layout_width="20dp"
android:layout_height="17dp"
android:layout_centerVertical="true"
android:layout_marginLeft="17dp"
android:src="@drawable/address_icon" />
</RelativeLayout>

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginTop="12dp"
android:layout_marginRight="62dp"
android:background="@drawable/blue_border_rounded_cornwe"
tools:ignore="MissingConstraints">
<EditText
android:id="@+id/edt_disease"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:layout_toRightOf="@id/username_icon6"
android:hint="Disease "
android:inputType="textEmailAddress"
android:maxLines="1"
android:paddingLeft="17dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:textSize="13sp">
</EditText>
<ImageView
android:id="@+id/username_icon6"
android:layout_width="20dp"
android:layout_height="17dp"
android:layout_centerVertical="true"
android:layout_marginLeft="17dp"
android:src="@drawable/disease_icon" />
</RelativeLayout>

<Button
android:id="@+id/btn_save"
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:textSize="18dp"
android:textAllCaps="false"
android:layout_marginTop="15dp"
android:text="Save"/>
<Button
android:id="@+id/btn_update"
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:textSize="18dp"
android:textAllCaps="false"
android:layout_marginTop="15dp"
android:text="Update"/>
<Button
android:id="@+id/btn_delete"
android:layout_width="150dp"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:textSize="18dp"
android:textAllCaps="false"
android:layout_marginTop="15dp"
android:text="Delete"/>

</LinearLayout>

In the patient_list.xml we can create the UI screen for customized items.

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="wrap_content"
android:layout_marginLeft="-3dp"
android:layout_marginRight="0dp"
android:layout_marginBottom="5dp"
app:cardCornerRadius="8dp"
app:cardElevation="3dp"
app:contentPadding="5dp"
tools:ignore="RtlHardcoded">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="@+id/patientrecord_viewholder_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginTop="10dp"
android:textSize="18sp"
android:text="id: "
android:textColor="@color/purple_500"
android:textStyle="bold" />
<TextView
android:id="@+id/txt_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textStyle="bold"
android:textColor="@color/purple_500"
android:text="name: " />
<TextView
android:id="@+id/txt_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textStyle="bold"
android:textColor="@color/purple_500"
android:text="age: " />
<TextView
android:id="@+id/txt_gender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textStyle="bold"
android:textColor="@color/purple_500"
android:text="gender: " />
<TextView
android:id="@+id/txt_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textStyle="bold"
android:textColor="@color/purple_500"
android:text="phone number: " />
<TextView
android:id="@+id/txt_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textStyle="bold"
android:textColor="@color/purple_500"
android:text="address: " />
<TextView
android:id="@+id/txt_disease"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:textStyle="bold"
android:textColor="@color/purple_500"
android:text="disease: " />
</LinearLayout>

</androidx.cardview.widget.CardView>

Demo

For result, please click here and check Demo in original content.

Tips and Tricks

1. Make sure you are already registered as Huawei developer.

2. Set minSDK version to 24 or later, otherwise you will get AndriodManifest merge issue.

3. Make sure you have added the agconnect-services.json file to app folder.

4. Make sure you have added SHA-256 fingerprint without fail.

5. Make sure all the dependencies are added properly.

Conclusion

In this article, we have learned that how to send push notifications using this Huawei Push Kit to the device in the Patient Tracking app. Users can add patient details in this app, so the data will be saved in the room database, it can be accessed offline also. User can easily track their patient list who are visited the hospital. In this app, users can add, update, delete and fetch operations.

I hope you have read this article. If you found it is helpful, please provide likes and comments.

Reference

Push Kit – Document

Push Kit – Training Video

Room Database