[Huawei Developers] John Wo:
In the previous part we have prepared our backend to receive messages and deliver them to the proper user. As many apps, our application needs an uthentication system, so only verified users will be able to use the app and appliy for a nickname. In this part, we will add the account system to our Messenger app.

Adding Dependencies

We will use AGC Auth Service to allow the users sing in with Huawei ID, Mail and Phone Number, let's add the AGC Auth Service and Account kit Dependencies

implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'
implementation 'com.huawei.hms:hwid:5.0.1.301'

App Startup

Auth Service will keep the user logged in the app until the user perform a log out operation. We must verify if there is a logged user upon each app startup. Also we must check if the user has choosen a nickname to be identified by other users.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/002/383/434/2640052000002383434.20201102014702.23154116017260885591943158413372:50511112015810:2800:C72DD93DC2F19262655D5513B1CB8C1B0771C272E5E084259B4882879CBDCC4A.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/002/383/434/2640052000002383434.20201102014702.23154116017260885591943158413372:50511112015810:2800:C72DD93DC2F19262655D5513B1CB8C1B0771C272E5E084259B4882879CBDCC4A.png[/IMG]

The Auth Service has an API to quickly check if there is a user already signed in the application.

val user = AGConnectAuth.getInstance().currentUser

If the user is null, we must display the sign in page, by other way, we must check if the user has choosen a nickname.

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val user = AGConnectAuth.getInstance().currentUser
if(user!=null)checkNickName(user.uid)
else redirectToLogin()

}
}

Building the login activity

For now, this page will only contain 2 buttons, one for sign in with Huawei, and other to use a mobile phone or email.

Note:The code will be designed for supporting phone number, but full phone number support will be added later.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/002/383/434/2640052000002383434.20201102024452.57993505076264596764147386103436:50511112015810:2800:7924AF96A93D0E9754CB01ECD7CD221DE0C12A80F7339B7592708DF4411F27EC.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/002/383/434/2640052000002383434.20201102024452.57993505076264596764147386103436:50511112015810:2800:7924AF96A93D0E9754CB01ECD7CD221DE0C12A80F7339B7592708DF4411F27EC.png[/IMG]

<?xml version="1.0" encoding="utf-8"?>
<layout
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">

<data class="LoginBinding">
<variable
name="loginVM"
type="com.hms.demo.messengerapp.LoginViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity"
android:padding="16dp">

<com.huawei.hms.support.hwid.ui.HuaweiIdAuthButton
android:id="@+id/huaweiIdAuthButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="@{()->loginVM.huaweiIdLogin(context)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />

<ImageView
android:id="@+id/imageView"
android:layout_width="232dp"
android:layout_height="439dp"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
app:srcCompat="@drawable/online_worlds" />

<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/mailPhoneLogin"
android:onClick="@{()->loginVM.mailPhoneLogin()}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loginTitle"
android:textAlignment="center"
android:textSize="28sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Now we need to add a ViewModel which will listen the buttons

class LoginViewModel : ViewModel() {
companion object{
const val HW_ID=100
const val MAIL_PHONE=200
}

val navigator:ViewNavigator?=null

public fun huaweiIdLogin(context: Context){
val authParams = HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setAuthorizationCode()
.createParams()
val service = HuaweiIdAuthManager.getService(context, authParams)
navigator?.navigateForResult(service.signInIntent, HW_ID)

}

public fun mailPhoneLogin(context: Context){
val intent=Intent(context, MailPhoneLoginActivity::class.java)
navigator?.navigateForResult(intent,MAIL_PHONE)
}

fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?){
when(requestCode){
HW_ID -> handleHuaweiID(data)
MAIL_PHONE -> handleMailPhoneUser(resultCode)
}
}

private fun handleHuaweiID(data: Intent?) {
val authHuaweiIdTask = HuaweiIdAuthManager.parseAuthResultFromIntent(data)
if (authHuaweiIdTask.isSuccessful) {
val huaweiAccount = authHuaweiIdTask.result
val credential = HwIdAuthProvider.credentialWithToken(huaweiAccount.accessToken)
AGConnectAuth.getInstance().signIn(credential).addOnSuccessListener{
askForNickName()
}
}
}

private fun handleMailPhoneUser(resultCode: Int) {
if(resultCode==200){
askForNickName()
}

}

private fun askForNickName() {

}

interface ViewNavigator{
fun navigateForResult(intent: Intent,requestCode:Int)

}

}

The login activity will connect the layout with the viewModel

class LoginActivity : AppCompatActivity(),LoginViewModel.ViewNavigator{
lateinit var viewModel:LoginViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding=LoginBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel =ViewModelProviders.of(this).get(LoginViewModel::class.java)
binding.loginVM=viewModel
}

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

}
override fun navigateForResult(intent: Intent, requestCode: Int) {
startActivityForResult(intent,requestCode)
}
}

MailPhoneLoginActivity

The MailPhoneLoginActivity will request and wait for a verification code for mail or phone and if the verification is complete, will set a result with a result code 200.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/002/383/434/2640052000002383434.20201103040203.28083073347799585635614291317107:50511112015810:2800:F604225F9BDC2B94E5D7A23AF3E24BDE71FB3FC753FBB2DCFC02E2E8A9B5494B.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/002/383/434/2640052000002383434.20201103040203.28083073347799585635614291317107:50511112015810:2800:F604225F9BDC2B94E5D7A23AF3E24BDE71FB3FC753FBB2DCFC02E2E8A9B5494B.png[/IMG]

The layout code is as follows

<?xml version="1.0" encoding="utf-8"?>

<layout
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">
<data class="MailPhoneBinding">
<variable
name="viewModel"
type="com.hms.demo.messengerapp.MailPhoneVM" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout

android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MailPhoneLoginActivity"
android:padding="16dp">

<EditText
android:id="@+id/inputCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:ems="10"
android:hint="@string/verificationMessage2"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView3" />

<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:text="@string/verificationMessage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView4" />

<TextView
android:id="@+id/textView4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/verificationTitle"
android:textAlignment="center"
android:textSize="28sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/verify"
android:onClick="@{()->viewModel.verifyCode(inputCode.text)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<EditText
android:id="@+id/inputMailPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="76dp"
android:ems="10"
android:hint="@string/mailPhone"
android:inputType="textPersonName"
app:layout_constraintEnd_toStartOf="@+id/button3"
app:layout_constraintHorizontal_bias="0.792"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />

<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="72dp"
android:layout_marginEnd="44dp"
android:onClick="@{()->viewModel.getCode(inputMailPhone.text)}"
android:text="@string/getCode"
android:textAllCaps="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />

<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="64dp"
android:text="@string/verificationMessage1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/inputMailPhone" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Once the code is entered, th user can press the verify button to create an AGCUser on the ViewModel

class MailPhoneVM : ViewModel() {
companion object{
val mailRegex = Regex("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}\$")
val phoneRegex=Regex("^\\+[1-9]{1}[0-9]{3,14}\$\n")
const val ACCOUNT_TYPE="account_type"
const val VALUE="value"
const val MAIL="mail"
const val PHONE="phone"
}
var navigator:MailPhoneNavigator?=null
private var dataType=MAIL
private var value=""

fun getCode(editable: Editable){

if (mailRegex.matches(editable.toString())) {
//is mail
getMailCode(editable.toString())
}
else if (phoneRegex.matches(editable.toString())){
//is phone
}
}

private fun getMailCode(mail: String) {
dataType= MAIL
value=mail
val settings = VerifyCodeSettings.newBuilder()
.action(VerifyCodeSettings.ACTION_REGISTER_LOGIN) //ACTION_REGISTER_LOGIN/ACTION_RESET_PASSWORD
.sendInterval(30) // Minimum sending interval, ranging from 30s to 120s.
.build()

val task: Task<VerifyCodeResult> = EmailAuthProvider.requestVerifyCode(
mail,
settings
)
task.addOnSuccessListener {
//The verification code application is successful.
}
.addOnFailureListener {
Log.e("EmailAuth", it.toString())
}
}

private fun getMobileCode(phone:String){
dataType= PHONE
value=phone
}

fun verifyCode(editable: Editable){
when(dataType){
PHONE -> verifyPhone(value,editable.toString())
MAIL -> verifyMail(value,editable.toString())
}
}

private fun verifyMail(email: String,code:String) {
val emailUser = EmailUser.Builder()
.setEmail(email)
.setVerifyCode(code)
.build()

AGConnectAuth.getInstance().createUser(emailUser)
.addOnSuccessListener{
navigator?.sendResult(200)
}
.addOnFailureListener{
Log.e("AuthSevice","Email Sign in failed $it")
}

}

private fun verifyPhone(phone: String,code:String) {
}

interface MailPhoneNavigator{
fun sendResult(result:Int)
}
}

Finally, we must connect everything on the activity

class MailPhoneLoginActivity : AppCompatActivity(), MailPhoneVM.MailPhoneNavigator {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = MailPhoneBinding.inflate(layoutInflater)
setContentView(binding.root)
val vm=ViewModelProviders.of(this).get(MailPhoneVM::class.java)
vm.navigator=this
binding.viewModel=vm
}

override fun sendResult(result: Int) {
setResult(result)
finish()
}
}

Conclusion

In this part we have integrated the Huawei Auth Service to allow users quickly create an account in our app. Stay tunned for the next part.