DEV Community

HMS Community
HMS Community

Posted on

Save the patient details on notification by Huawei Push Kit in the Patient Tracking Android app (Kotlin) – Part 2

Image description
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.

Image description

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

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

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

  • Generate a SHA-256 certificate fingerprint.

  • 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.

Image description

Note: Project Name depends on the user created name.

Image description

  • Enter SHA-256 certificate fingerprint and click Save button, as follows.
    Image description

  • Click Manage APIs tab and enable Push Kit.
    Image description

  • 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'

Enter fullscreen mode Exit fullscreen mode
  • 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'
Enter fullscreen mode Exit fullscreen mode
  • Now Sync the gradle.

  • 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>
Enter fullscreen mode Exit fullscreen mode

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"
    }

}
Enter fullscreen mode Exit fullscreen mode

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"
    }

}
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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)

}
Enter fullscreen mode Exit fullscreen mode

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
            }
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

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)
    }

}

Enter fullscreen mode Exit fullscreen mode

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)

}
Enter fullscreen mode Exit fullscreen mode

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) }
        })

    }

}
Enter fullscreen mode Exit fullscreen mode

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"
    }

}
Enter fullscreen mode Exit fullscreen mode

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()
        }


    }

}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Demo

Image description

Image description

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

Top comments (0)