DEV Community

HMS Community
HMS Community

Posted on

Expert: Courier App MVVM Jetpack (HMS Account and AuthService) in Android using Kotlin- Part-1

Overview

In this article, I will create a Courier android application using Kotlin in which I will integrate HMS Core kits such as HMS Account and AuthService Kit.

App will make use of android MVVM clean architecture using Jetpack components such as DataBinding, AndroidViewModel, Observer, LiveData and much more.

In this article, we are going to implement DataBinding using Observable pattern.

Huawei ID Service Introduction

Huawei ID login provides you with simple, secure, and quick sign-in and authorization functions. Instead of entering accounts and passwords and waiting for authentication, users can just tap the Sign in with HUAWEI ID button to quickly and securely sign in to your app with their HUAWEI IDs.

Prerequisite

Huawei Phone EMUI 3.0 or later.
Non-Huawei phones Android 4.4 or later (API level 19 or higher).
HMS Core APK 4.0.0.300 or later
Android Studio
AppGallery Account

App Gallery Integration process

Sign In and Create or Choose a project on AppGallery Connect portal.

Navigate to Project settings and download the configuration file.

Navigate to General Information, and then provide Data Storage location.

App Development

Add Required Dependencies:
Launch Android studio and create a new project. Once the project is ready.
Navigate to the Gradle scripts folder and open build.gradle (module: app).

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.huawei.agconnect'

android {
    compileSdkVersion 30
    buildToolsVersion "29.0.3"

    buildFeatures {

        dataBinding = true
        viewBinding = true
    }
Enter fullscreen mode Exit fullscreen mode

In the same build.gradle file, add the lifecycle library to your dependencies. This library helps connect the UI to a ViewModel and LiveData.

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    //noinspection GradleDependency
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.6.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'androidx.annotation:annotation:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
Enter fullscreen mode Exit fullscreen mode

Add following dependency for HMS Kits

    //HMS Kits
    implementation 'com.huawei.agconnect:agconnect-core:1.5.0.300'
    implementation 'com.huawei.hms:hwid:5.3.0.302'
Enter fullscreen mode Exit fullscreen mode

Navigate to the Gradle scripts folder and open build.gradle (project: app).

classpath 'com.huawei.agconnect:agcp:1.4.2.300'
maven {url 'https://developer.huawei.com/repo/'}
Enter fullscreen mode Exit fullscreen mode

Code Implementation
Created following package model, event, viewmodel.
Model: In your primary folder, create a new package and name it model. Then create a User.kt file in this package.

data class User(
    var from: String,
    var to: String
)
Enter fullscreen mode Exit fullscreen mode

ViewModel: The ViewModel makes easy to update data changes on the UI. Create a package named viewmodel in your main folder. Then create a new file and name it LoginViewModel.kt, OrderViewModel.kt along with their FactoryViewModelProviders.

LoginViewModel.kt:

package com.hms.corrieraap.viewmodel

import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.databinding.Observable
import androidx.lifecycle.AndroidViewModel
import com.hms.corrieraap.OrderActivity
import com.hms.corrieraap.event.ActivityNavigation
import com.hms.corrieraap.event.LiveMessageEvent
import com.huawei.hmf.tasks.Task
import com.huawei.hms.support.account.AccountAuthManager
import com.huawei.hms.support.account.request.AccountAuthParams
import com.huawei.hms.support.account.request.AccountAuthParamsHelper
import com.huawei.hms.support.account.result.AuthAccount
import com.huawei.hms.support.account.service.AccountAuthService

const val HMS_SIGN_IN: Int = 9001


class LoginViewModel(application: Application) : AndroidViewModel(application), Observable {

    private var mAuthManager: AccountAuthService? = null
    private var mAuthParam: AccountAuthParams? = null

    val startActivityForResultEvent = LiveMessageEvent<ActivityNavigation>()

    fun login() {
        mAuthParam = AccountAuthParamsHelper(AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
            .setIdToken()
            .setAccessToken()
            .createParams()
        mAuthManager = AccountAuthManager.getService(Activity(), mAuthParam)
        startActivityForResultEvent.sendEvent {
            startActivityForResult(
                mAuthManager?.signInIntent,
                HMS_SIGN_IN
            )
        }
    }

    fun onResultFromActivity(context: Context, requestCode: Int, data: Intent?) {
        when (requestCode) {
            HMS_SIGN_IN -> {
                val authAccountTask = AccountAuthManager.parseAuthResultFromIntent(data)
                onCompleteLogin(context, authAccountTask)
            }
        }
    }

    private fun onCompleteLogin(context: Context, doneTask: Task<AuthAccount>) {
        if (doneTask.isSuccessful) {
            val authAccount = doneTask.result
            Log.d("LoginViewModel", "SigIn Success")
            context.startActivity(Intent(context, OrderActivity::class.java))

        } else {
            Log.d("LoginViewModel", "SigIn Error")
        }
    }

    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
    }

    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
    }

}
Enter fullscreen mode Exit fullscreen mode

LoginViewModelFactory.kt:

package com.hms.corrieraap.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider


class LoginViewModelFactory : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if(modelClass.isAssignableFrom(LoginViewModel::class.java)){
            return LoginViewModel() as T
        }
        throw IllegalArgumentException ("UnknownViewModel")
    }

}
Enter fullscreen mode Exit fullscreen mode

OrderViewModel.kt:


package com.hms.corrieraap.viewmodel

import androidx.databinding.Bindable
import androidx.databinding.Observable
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.hms.corrieraap.model.User


class OrderViewModel : ViewModel(), Observable {

    @Bindable
    val from = MutableLiveData<String>()

    @Bindable
    val to = MutableLiveData<String>()

    var data = MutableLiveData<User>()

    fun onDataChanged() {
        val from = from.value!!
        val to = to.value!!

        val user = User(from, to)
        data.value = user
    }

    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
        TODO("Not yet implemented")
    }

    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
        TODO("Not yet implemented")
    }
}
Enter fullscreen mode Exit fullscreen mode

OrderViewModelFactory.kt:

package com.hms.corrieraap.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import java.lang.IllegalArgumentException


class OrderViewModelFactory : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(OrderViewModel::class.java)){
            return OrderViewModel() as T
        }
        throw IllegalArgumentException("UnknownViewModel")
    }
}
Enter fullscreen mode Exit fullscreen mode

Xml layout DataBinding
To include data binding in the UI, enclose all content with .

The ViewModel is introduced to the layout in the section, as shown. Ensure that the type value points to the specific folder that has the required ViewModel.

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="LoginViewModel"
            type="com.hms.corrieraap.viewmodel.LoginViewModel" />
    </data>
Enter fullscreen mode Exit fullscreen mode

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="LoginViewModel"
            type="com.hms.corrieraap.viewmodel.LoginViewModel" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary">

        <com.google.android.material.appbar.AppBarLayout
            style="@style/AppTheme.AppBarOverlay"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                style="@style/AppTheme.PopupOverlay"
                android:layout_width="match_parent"
                android:layout_height="?actionBarSize"
                android:background="@color/colorPrimaryDark" />
        </com.google.android.material.appbar.AppBarLayout>

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:gravity="center">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                android:padding="16dp">


                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:padding="5dp"
                    android:text="SigIn / SignUp "
                    android:textAlignment="center"
                    android:textColor="@color/white"
                    android:textSize="34sp"
                    android:textStyle="bold" />


                <Button
                    android:id="@+id/btn_sign"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="20dp"
                    android:layout_marginBottom="5dp"
                    android:background="@color/colorPrimaryDark"
                    android:text="Login With Huawei Id"
                    android:onClick="@{()->LoginViewModel.login()}"
                    android:textColor="@color/white"
                    android:textStyle="bold" />
            </LinearLayout>

        </ScrollView>

    </RelativeLayout>
</layout>
Enter fullscreen mode Exit fullscreen mode

activity_order.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="orderViewModel"
            type="com.hms.corrieraap.viewmodel.OrderViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.google.android.material.appbar.AppBarLayout
            style="@style/AppTheme.AppBarOverlay"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                style="@style/AppTheme.PopupOverlay"
                android:layout_width="match_parent"
                android:layout_height="?actionBarSize"
                android:background="@color/colorPrimaryDark" />
        </com.google.android.material.appbar.AppBarLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/activity_vertical_margin"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/heading_from_text_view"
                android:layout_width="60dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/activity_horizontal_margin"
                android:layout_marginTop="@dimen/activity_vertical_margin"
                android:layout_marginRight="@dimen/activity_horizontal_margin"
                android:text="@string/heading_from"
                android:textAllCaps="true"
                android:textSize="20sp" />

            <FrameLayout
                android:id="@+id/place_autocomplete_frame"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/activity_vertical_margin"
                android:layout_weight="1">

                <TextView
                    android:id="@+id/place_autocomplete_from"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:hint="@string/hint_address"
                    android:text="@={orderViewModel.from}"
                    android:textSize="20sp" />
            </FrameLayout>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/activity_vertical_margin"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/heading_to_text_view"
                android:layout_width="60dp"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/activity_horizontal_margin"
                android:layout_marginTop="@dimen/activity_vertical_margin"
                android:layout_marginRight="@dimen/activity_horizontal_margin"
                android:text="@string/heading_to"
                android:textAllCaps="true"
                android:textSize="20sp" />

            <FrameLayout
                android:id="@+id/place_autocomplete_frame2"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/activity_vertical_margin"
                android:layout_weight="1">

                <TextView
                    android:id="@+id/place_autocomplete_to"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:hint="@string/hint_address"
                    android:text="@={orderViewModel.to}"
                    android:textSize="20sp" />
            </FrameLayout>
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/activity_vertical_margin"
            android:layout_marginBottom="@dimen/activity_vertical_margin"
            android:gravity="center_horizontal"
            android:orientation="horizontal"
            android:weightSum="1.0">

            <Button
                android:id="@+id/neworder_button_calculate"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="start"
                android:layout_marginEnd="0dp"
                android:layout_marginRight="0dp"
                android:layout_weight="0.4"
                android:text="@string/heading_calculate" />

            <Button
                android:id="@+id/neworder_button_additional"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_weight="0.4"
                android:text="@string/neworder_alert_additional" />

            <Button
                android:id="@+id/neworder_button_call"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="end"
                android:onClick="@{()->orderViewModel.onDataChanged()}"
                android:layout_weight="0.4"
                android:text="@string/heading_call" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/neworder_textview_summary"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="@dimen/activity_vertical_margin"
                android:layout_marginBottom="@dimen/activity_vertical_margin"
                android:layout_weight="0.2"
                android:gravity="center_horizontal"
                android:textAlignment="center"
                android:textAllCaps="true" />
        </LinearLayout>

    </LinearLayout>
</layout>
Enter fullscreen mode Exit fullscreen mode

LiveMessageEvent: Implemented LiveMessageEvent to provide LifeCycleOwner

package com.hms.corrieraap.event

import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer

class LiveMessageEvent<T> : SingleLiveEvent<(T.() -> Unit)?>() {

    fun setEventReceiver(owner: LifecycleOwner, receiver: T) {
        observe(owner, Observer { event ->
            if (event != null) {
                receiver.event()
            }
        })
    }

    fun sendEvent(event: (T.() -> Unit)?) {
        value = event
    }
}
Enter fullscreen mode Exit fullscreen mode

SingleEvent: Implemented SingleLiveEvent for LifeCycleObserver

package com.hms.corrieraap.event

import android.util.Log

import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean

open class SingleLiveEvent<T> : MutableLiveData<T>() {

    private val mPending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {

        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
        }

        super.observe(owner, Observer { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    @MainThread
    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    @MainThread
    fun call() {
        value = null
    }

    companion object {

        private val TAG = "SingleLiveEvent"
    }
}
Enter fullscreen mode Exit fullscreen mode

ActivityNavigation.kt: Implemented interface for activity transition.

package com.hms.corrieraap.event

import android.content.Intent


interface ActivityNavigation {
    fun startActivityForResult(intent: Intent?, requestCode: Int)
}
Enter fullscreen mode Exit fullscreen mode

​​​​​​​

MainActivity.kt: Implemented for Huawei ID login.

package com.hms.corrieraap

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProviders
import com.hms.corrieraap.databinding.ActivityMainBinding
import com.hms.corrieraap.event.ActivityNavigation
import com.hms.corrieraap.viewmodel.LoginViewModel
import com.hms.corrieraap.viewmodel.LoginViewModelFactory

class MainActivity : AppCompatActivity(), ActivityNavigation {

    private lateinit var viewModel: LoginViewModel
    private lateinit var dataBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        val factory = LoginViewModelFactory()
        viewModel = ViewModelProviders.of(this, factory).get(LoginViewModel::class.java)
        dataBinding.loginViewModel = viewModel
        dataBinding.lifecycleOwner=this
        viewModel.startActivityForResultEvent.setEventReceiver(this, this)
    }


    public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        viewModel.onResultFromActivity(requestCode, data)
        super.onActivityResult(requestCode, resultCode, data)
    }

}
Enter fullscreen mode Exit fullscreen mode

OrderActivity.kt: Implemented for Order new Courier.

```package com.hms.corrieraap

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.hms.corrieraap.databinding.ActivityOrderBinding
import com.hms.corrieraap.viewmodel.OrderViewModel
import com.hms.corrieraap.viewmodel.OrderViewModelFactory

class OrderActivity : AppCompatActivity() {

private lateinit var activityOrderBinding: ActivityOrderBinding
private lateinit var orderViewModel: OrderViewModel

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    activityOrderBinding = DataBindingUtil.setContentView(this, R.layout.activity_order)
    val factory = OrderViewModelFactory()
    orderViewModel = ViewModelProviders.of(this, factory).get(OrderViewModel::class.java)
    activityOrderBinding.lifecycleOwner = this

    orderViewModel.data.observe(this, Observer {
        Toast.makeText(this, "Placed Order", Toast.LENGTH_SHORT).show();
    })

}
Enter fullscreen mode Exit fullscreen mode

}```

​​​​​​​
**
App Build Result**

Image description

Tips and Tricks

Identity Kit displays the HUAWEI ID registration or sign-in page first. The user can use the functions provided by Identity Kit only after signing in using a registered HUAWEI ID.

Conclusion

In this article, we have learned how to integrate Huawei ID in Android application. After completely read this article user can easily implement Huawei ID in the Courier android application using Kotlin.

Thanks for reading this article. Be sure to like and comment to this article, if you found it helpful. It means a lot to me.

References

HMS Docs:

https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/introduction-0000001050048870

Account Kit Training Video:

https://developer.huawei.com/consumer/en/training/course/video/101603872818951100

Top comments (0)