Writing a Serverless Android app (ft. Huawei's AppGallery Connect) - Part 1 - Auth

Not read part 0? Read it first!

Over the next couple of months I will be releasing a complete end to end guide to creating an Android app using serverless functionality to completely remove the need for any backend server/hosting etc.

This weeks Video is below

And at the bottom of this post you will find my twitch channel where we live stream every Thursday at 2pm BST and the github repo for this project!.

But for those that would rather a written guide, lets get into it!

Project Setup

Starting with a brand new project (or one that has never used Huawei services) we will need to start by setting up a new app. (If you haven't setup your developer account yet sign up!)

If you would like the complete step by step guide on how to get setup check out the Official Documentation, but below is a summery.

Navigate to the the AppGallery Connect area of the developer portal, here you need to create a new project.
image

Follow the new project guide, giving that project a name. Once completed on the left hand menu find the Auth service under the build menu.
image
Click the enable now button in the top right corner to enable this service for your project. Set a default data processing location, depending on your physical location will most likely help decide which to use. For us we have selected Germany as this is the closest location and within the EU.

From here you are presented with the list of authentication modes, Huawei supports a wide range of services from Facebook login to AppleID. However today we are focusing on the email auth method. Click the enable button next to this.
image

Now that everything is setup in the project, its time to setup the app! At this point I will assume you have created a new blank project in Android Studio and given it a package name etc.
From the project settings top screen, select the Add app button to setup a new app under this project.
image
Fill in the add app form, setting the platform, app name, package etc so that it can be added to the created project. Its worth noting at this point, that while we are focusing on Android today many of these services can be used across multiple platforms including iOS and web.
image
Once completed you will be told to download the agconnect-services.json file and presented with a code to get the core services setup. As we are already focusing on a specific service for today the setup at this point is a little different.
Start by placing your nearly downloaded agconnect-services.json file into the app directory of your project.
image

Next we will get gradle configured correctly. In your top level gradle build file add the below to your repositories both under buildscript and allprojects

maven {
    url 'https://developer.huawei.com/repo/'
}

And to your dependencies add classpath 'com.huawei.agconnect:agcp:1.4.1.300'

Your file should now look a little like

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()
        mavenCentral()
        maven {
            url 'https://developer.huawei.com/repo/'
        }
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.2.2"
        classpath 'com.huawei.agconnect:agcp:1.4.1.300'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
        jcenter() // Warning: this repository is going to shut down soon
        maven {
            url 'https://developer.huawei.com/repo/'
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Next open your app build.gradle file

Start by adding the agconnect plugin into your plugins list

plugins {
    id 'com.android.application'
    id 'com.huawei.agconnect'
}

Make sure to set your midSdkVersion to at least 17 (this is required for any Huawei AppGallery Connect services.

And then in dependencies we are going to add the core and auth services

implementation 'com.huawei.agconnect:agconnect-core:1.4.1.300'
    implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'

So your file should now look something like

plugins {
    id 'com.android.application'
    id 'com.huawei.agconnect'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "site.zpweb.barker"
        minSdkVersion 17
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'com.huawei.agconnect:agconnect-core:1.4.1.300'
    implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'
    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

And thats it! let gradle sync up and we are all good to go!

Registration

As I mentioned before there are a wide range of ways a user can authenticate using the Auth service, but here we are looking at the email service. Email authenticate the user by sending them a code via (your guessed it) Email. The user then inputs this code back into the app to confirm that they are who the say they are, and that they have access to that contact method. We will assume you have setup some kind of register view that will capture a users email.

We start by requesting an authentication code be set to the user, the code to do this looks like

VerifyCodeSettings settings = VerifyCodeSettings.newBuilder()
                .action(VerifyCodeSettings.ACTION_REGISTER_LOGIN)
                .sendInterval(30)
                .locale(Locale.ENGLISH)
                .build();
Task<VerifyCodeResult> task = EmailAuthProvider.requestVerifyCode(emailString, settings);
            task.addOnSuccessListener(TaskExecutors.uiThread(), new OnSuccessListener<VerifyCodeResult>() {
                @Override
                public void onSuccess(VerifyCodeResult verifyCodeResult) {
                    authCodeDialog();
                }
            }).addOnFailureListener(TaskExecutors.uiThread(), new OnFailureListener() {
                @Override
                public void onFailure(Exception e) {
                    Toast.makeText(RegisterActivity.this,
                            "Error, code sending failed: " + e,
                            Toast.LENGTH_LONG).show();
                }
            });

Lets break this down, we start by defining a VerifyCodeSettings object, this holds all the settings relating to the sending of the code. Here we define what locale should be used for the message text that is sent, what kind of code it is and the send interval.

Next we create a task to be run

Task<VerifyCodeResult> task = EmailAuthProvider.requestVerifyCode(emailString, settings);

Using the EmailAuthProvider, where emailString is the email address the user has entered, as a string and the settings object is the VerifyCodeSettings object we just created.

Next we setup an OnSucessListener which will be called if the code was successfully sent to the user. In this example we are calling the method authCodeDialog(); to display a dialog to enter the code, which will see in a moment.
We also setup an OnFailureListener which simply create a toast on screen to display what ever error is sent back.

Now that we have sent the user a code we should display some view for them to enter that code, in my instance I have the method below, but of course this could be what ever view you wanted.

private void authCodeDialog() {
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        final EditText authCodeField =  new EditText(this);
        alert.setMessage("Enter your auth code below");
        alert.setTitle("Authentication Code");

        alert.setView(authCodeField);

        alert.setPositiveButton("Register", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                String authCode = authCodeField.getText().toString();
                register(authCode);
            }
        });

        alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(RegisterActivity.this,
                        "Registration Cancelled",
                        Toast.LENGTH_LONG).show();
            }
        });

        alert.show();
    }

Finally once the user has inputted the code we can register them as you can see above we have a register method that is called, this has the below code:

EmailUser emailUser = new EmailUser.Builder()
                    .setEmail(emailString)
                    .setVerifyCode(authCode)
                    .build();

            AGConnectAuth.getInstance().createUser(emailUser).addOnSuccessListener(new OnSuccessListener<SignInResult>() {
                @Override
                public void onSuccess(SignInResult signInResult) {
                    Toast.makeText(RegisterActivity.this,
                            "Register Successful: " + signInResult.getUser().getUid(),
                            Toast.LENGTH_LONG);
                }
            }).addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(Exception e) {
                    Toast.makeText(RegisterActivity.this,
                            "Registering failed " + e,
                            Toast.LENGTH_LONG);
                }
            });

So we start by creating an EmailUser using the email address we captured earlier and the authCode that the user has entered. Using that EmailUser we then attempt to register. Note that we can also set a password against the EmailUser, if we do this when they go to login they do not need to verify their email address again. They can just enter their email and password.

As you can see we pass the created object into the createUser method, attaching OnSuccess and OnFailure Listeners. If the user is successfully registered, i.e the code they entered matches what was sent we are returned a SignInResult this object containes the user. So in this example we simply print out the registered users UID to confirm it completed successfully.

Login

Now that we have covered the sign up process lets look at how we might handle a login process. This would assume that the user has already registered via the above method and is now logging into the app, perhaps after installing it on a new phone.

As I mentioned before if the user signed up without a password then start by sending a verify code, in just the same way as we did during the sign up process. Once we have the code we can generate a AGCOnnectAuthCredential object

AGConnectAuthCredential credential = credential = EmailAuthProvider.credentialWithVerifyCode(
                            email.getText().toString().trim(),
                            null,
                            authCode);

Here we get the email that the user entered (email being an EditText object). We pass null for the password as we haven't used this, and finally the authCode which the user has entered into the app.

Now we can attempt the sign the user in:

AGConnectAuth.getInstance().signIn(credential)
                .addOnSuccessListener(new OnSuccessListener<SignInResult>() {
                    @Override
                    public void onSuccess(SignInResult signInResult) {
                        Toast.makeText(MainActivity.this, "Sign in successful: " +
                                signInResult.getUser().getUid(), Toast.LENGTH_LONG);
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(Exception e) {
                        Toast.makeText(MainActivity.this, "sign in failed:" + e, Toast.LENGTH_LONG);
                    }
                });

We pass the credential object into the signIn method, if the code and email matched and was correct the OnSuccessListener will return the SignInResult object just as it did during the sign up, from here we have access to the users detials.
If for any reason the login fails we will get an Exception in the OnFailureListener.

And thats it! We have setup the application to use Huawei services and configured the app to use email authentication during sign up and log in.

Twitch Acccount

Github Repo

Barker - A Serverless Twitter Clone

Barker is a simple twitter style social network written to demostrate the serverless services provided by Huawei's AppGallery Connect platform.

This app is written during live streams over at https://www.twitch.tv/devwithzachary The streams are then edited down into videos which you can watch https://www.youtube.com/channel/UC63PqG8ZnWC4JWYrNJKocMA

Finally there is also a written copy of the guide found on dev.to https://dev.to/devwithzachary

Each episodes code is commited as a single commit to make it easy to step along with the code, simply checkout the specific commit and follow along!

We will be back with the next part next week!

19