[Huawei Developers] Freemind R:
In a previous post I´ve Shown you how to Sign In with Huawei without HMS, now we are going to use the credentials provided by the OAUTH APIs to access Drive kit and list the user's available files. By using the REST APIs of Drive kit, your app will be able to work without depending on the HMS Core APK, even in non-huawei devices.

Previous requirements

A developer account

Adding the required dependencies

As we have seen on the previous article, we will require the dependencies of the AppAuth library and the latest version of account kit to get access to the "Huawei Id Sign In button".

implementation 'net.openid:appauth:0.7.1'
implementation 'com.squareup.okio:okio:1.15.0'
implementation 'com.huawei.agconnect:agconnect-core:1.4.1.300'
implementation 'com.huawei.hms:hwid:5.0.1.301'

In order to get access to the user's Huawei Drive, we must use Account Kit to apply for the related scopes. You can use this link to check the complete list of Drive Scopes, use the gradle dependency for Drive Kit to quickly access to all the Drive Scopes.

implementation 'com.huawei.hms:drive:5.0.0.302'

We will not enter in details about the setup of the App Auth library, you can refer to this article to easily complete the library configuration.

Building the Auth Request

We wil use our Huawei Sign In button to trigger the Auth reques when is pressed. To achieve that we need to connect the onClick event of the button with our ViewModel.

The data binding library can help us to directly connect the layout with the ViewModel, but this library must be enabled first on the app level buidl.gradle file.

android{

buildFeatures{
dataBinding true
viewBinding true
}
}

To use the data binding library with kotlin, we must also add the kapt plugin

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.huawei.agconnect'
id 'kotlin-kapt'
}

dependencies{
kapt 'com.android.databinding:compiler:3.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
}

Now is time to link the layout with the ViewModel

activity_main.xml

<?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>
<variable
name="mainVM"
type="com.hms.demo.appauthdrivekit.MainVM" />
</data>

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

<com.huawei.hms.support.hwid.ui.HuaweiIdAuthButton
android:id="@+id/hwidbtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
android:onClick="@{()->mainVM.onHwIdLogin(context)}"/>

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

<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="96dp"
android:text="@string/description"
android:textAlignment="center"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />

<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="28dp"
android:text="@string/instruction"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@+id/hwidbtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent" />

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

By using the variable tag we are preparing the ViewBinding object to receive and hold an instance of the ViewModel. To completly connect the ViewBinding with the ViewModel, we must obtain both from our activity and perform the assignation.

MainActivity.kt

class MainActivity : AppCompatActivity(), MainVM.LoginNavigator{
lateinit var viewModel:MainVM

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

Now the ViewModel is ready to listen the HwIdButton click events, when a click is detected, the Huawei OAuth request will be triggered.

MainVM.kt

class MainVM : ViewModel() {

var navigator:LoginNavigator?=null

fun onHwIdLogin(context: Context){
val appId = AGConnectServicesConfig.fromContext(context).getString(HuaweiUtils.KEY_APP_ID)
val params=HuaweiUtils.getParams(appId)
val authRequest=OAuthtils.buildRequest(params)
val authService = AuthorizationService(context)
val authIntent = authService.getAuthorizationRequestIntent(authRequest)
navigator?.navigateToHwLogin(authIntent)
authService.dispose()
}

interface LoginNavigator{
fun navigateToHwLogin(intent : Intent)
}
}

Tips & Tricks

If you want to use the App Auth library to sign in with other account systems, you can define a Data Class to hold all the required parameters by a given account system to build the auth request.

data class OAuthParams(var authEndpoint:String,
var tokenEndpoint:String,
var appId:String,
var responseTypeValues:String,
var redirectUri:Uri,
var scopes:List<String>)

object OAuthtils{

fun buildRequest(params:OAuthParams): AuthorizationRequest{

val serviceConfig = AuthorizationServiceConfiguration(
Uri.parse(params.authEndpoint), // authorization endpoint
Uri.parse(params.tokenEndpoint)// token endpoint
)
val authRequestBuilder = AuthorizationRequest.Builder(
serviceConfig, // the authorization service configuration
params.appId, // the client ID, typically pre-registered and static
params.responseTypeValues, //
params.redirectUri//The redirect URI
)
val stringBuilder= StringBuilder()
for(scope in params.scopes){
stringBuilder.append("$scope ")
}
authRequestBuilder.setScope(stringBuilder.toString())
return authRequestBuilder.build()

}
}

The related parameters for the Huawei Auth Request will be encapsulated on the HuaweiUtils object. Other objects can be defined to hold the params for other account systems which support OAuth.

object HuaweiUtils {
private const val AUTH_ENDPOINT = "https://oauth-login.cloud.huawei.com/oauth2/v3/authorize"
private const val TOKEN_ENDPOINT = "https://oauth-login.cloud.huawei.com/oauth2/v3/token"
private const val HW_REDIRECT_URI_PREFIX = "com.huawei.apps."
private const val HW_REDIRECT_URI_SUFFIX=":/oauth2redirect"
const val KEY_APP_ID="client/app_id"
const val HW_ID_CODE = 100

fun getParams(appId:String):OAuthParams{
val scopes= getHuaweiScopes()
val redirectUri=Uri.parse("${HW_REDIRECT_URI_PREFIX}appid${HW_REDIRECT_URI_SUFFIX}")
return OAuthParams(
AUTH_ENDPOINT,
TOKEN_ENDPOINT,
appId,
ResponseTypeValues.CODE,
redirectUri,
scopes
)
}

private fun getHuaweiScopes():List<String>{
return listOf("openid",
"email",
"profile",
DriveScopes.SCOPE_DRIVE,
DriveScopes.SCOPE_DRIVE_APPDATA,
DriveScopes.SCOPE_DRIVE_FILE,
DriveScopes.SCOPE_DRIVE_METADATA,
DriveScopes.SCOPE_DRIVE_METADATA_READONLY,
DriveScopes.SCOPE_DRIVE_READONLY
)
}
}

Connecting to Huawei Drive

We will use the Auth credentials obtained from the LoginActivity to connect with Huawei Drive. As you may know, the AccessToken obtaining via OAuth has an exiration time, the AppAuth library provides us an API to always use fresh tokens.

authState.performActionWithFreshTokens(service, new AuthStateAction() {
@Override public void execute(
String accessToken,
String idToken,
AuthorizationException ex) {
if (ex != null) { // negotiation for fresh tokens failed, check ex for more details
return;
} // use the access token to do something ...
}
});

Lets define the layout of our DriveActivity, we will need to know tthe identity of the user and the available space on Huawei Drive. The list of stored Items on Drive will be added later.

More details, you can visit