[Huawei Developers] Onur OKYAY:
HMS Account Kit with Provider Pattern in Flutter

Hello everyone,

In this article, we will develop a login screen with Huawei’s account kit. We will be using the provider pattern which is one of the most preferred patterns in Flutter. In the end, our demo application will look like below.

Demo Application

HMS Account Kit

Apps with HUAWEI Account Kit allow users to sign in using their HUAWEI IDs with just a tap. By integrating, you can attract new users, by leveraging the enormous HUAWEI ID user base. Account Kit complies with international standards and protocols such as OAuth2.0 and OpenID Connect. It supports two-factor authentication(password authentication and mobile number authentication) to ensure high security. For a detailed explanation please refer to the documentation.

Provider Pattern

Provider pattern is a simple app state management. The idea behind it is that you have a central store or data container in the app which is called provider. Once you added your provider, so this data container to a widget, all child widgets of that widget can listen to that provider. It contains some data and notifies observers when a change occurs.

Provider pattern gives us an easy, low boiler-plate way to separate business logic from our widgets in apps. In the demo application, we are going to have a provider that is called LoginProvider and we will have all the required methods over there. From the other widgets in the app, we will be able to reach the methods and data of it.

Integration Preparations

First of all, you need to register as a HUAWEI developer and verify your identity. Please refer to the link for details. After that, you need to integrate the HUAWEI HMS Core into your application.

Software Requirements

Android Studio 3.X or later

JDK 1.8 or later

SDK Platform 19 or later

Gradle 4.6 or later

The integration flow will be like this :

For a detailed HMS core integration process, you can refer to Preparations for Integrating HUAWEI HMS Core.

Implementation

On your Flutter project directory, open pubspec.yaml file and add the dependencies for Account kit and Provider package. In order to show toast messages on user login and logout actions, I have also added fluttertoast package as well.

dependencies:
flutter:
sdk: flutter
huawei_account: ^5.0.0+300
provider: ^4.3.2+2
fluttertoast: ^7.1.1

Login Provider

In Login provider, we have all the required methods to manage Account actions like sign in, sign out, silent sign in, and revoke authorization. It gives us the flexibility to use any of these methods wherever we desire in the application.

class LoginProvider with ChangeNotifier {
User _user = new User();

User get getUser {
return _user;
}

void signIn() async {
AuthParamHelper authParamHelper = new AuthParamHelper();
authParamHelper
..setIdToken()
..setAuthorizationCode()
..setAccessToken()
..setProfile()
..setEmail()
..setId()
..addToScopeList([Scope.openId])
..setRequestCode(8888);
try {
final AuthHuaweiId accountInfo = await HmsAccount.signIn(authParamHelper);
_user.id = accountInfo.unionId;
_user.displayName = accountInfo.displayName;
_user.email = accountInfo.email;
_user.profilePhotoUrl = accountInfo.avatarUriString;
notifyListeners();
showToast('Welcome ${_user.displayName}');
} on Exception catch (exception) {
print(exception.toString());
}
}

Future signOut() async {
final signOutResult = await HmsAccount.signOut();
if (signOutResult) {
_user.id = null;
notifyListeners();
showToast('Signed out');
} else {
print('Login_provider:signOut failed');
}
}

void silentSignIn() async {
AuthParamHelper authParamHelper = new AuthParamHelper();
try {
final AuthHuaweiId accountInfo =
await HmsAccount.silentSignIn(authParamHelper);
if (accountInfo.unionId != null) {
_user.id = accountInfo.unionId;
_user.displayName = accountInfo.displayName;
_user.profilePhotoUrl = accountInfo.avatarUriString;
_user.email = accountInfo.email;
notifyListeners();
showToast('Welcome ${_user.displayName}');
}
} on Exception catch (exception) {
print(exception.toString());
print('Login_provider:Can not SignIn silently');
}
}

Future revokeAuthorization() async {
final bool revokeResult = await HmsAccount.revokeAuthorization();
if (revokeResult) {
print('Login_provider:Revoked Auth Successfully');
} else {
print('Login_provider:Failed to Revoked Auth');
}
}

void showToast(String message) {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
backgroundColor: Colors.grey,
textColor: Colors.black,
fontSize: 16.0);
}
}

Login Screen

On the login screen page, we are going to try if we can sign in silently first. If the revoke authorization method was not called, then the app will sign in silently and will not ask for user permissions. So that the application’s login screen will be skipped and the profile page will appear on the screen. If the user clicked the button that is called signout, then we call both sign-out and revoke authorization methods of the Account kit in our use case here. As a result, the user will be redirected to the login screen.

class LoginScreen extends StatelessWidget {
static const routeName = '/login-screen';

@override
Widget build(BuildContext context) {
final loginProvider = Provider.of<LoginProvider>(context, listen: false);
loginProvider.silentSignIn();
return Consumer<LoginProvider>(
builder: (context, data, _) {
return data.getUser.id != null
? ProfileScreen()
: LoginWidget(loginProvider: loginProvider);
},
);
}
}

class LoginWidget extends StatelessWidget {
const LoginWidget({
Key key,
@required this.loginProvider,
}) : super(key: key);

final LoginProvider loginProvider;

@override
Widget build(BuildContext context) {
var screenSize = MediaQuery.of(context).size;
return Scaffold(
body: Stack(
children: [
Image.asset(
'assets/images/welcome.png',
fit: BoxFit.cover,
height: double.infinity,
width: double.infinity,
alignment: Alignment.center,
),
Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.only(bottom: screenSize.height / 6),
child: HuaweiIdAuthButton(
onPressed: () {
loginProvider.signIn();
},
buttonColor: AuthButtonBackground.BLACK,
borderRadius: AuthButtonRadius.MEDIUM,
),
)
],
),
);
}
}

Profile Screen

On the profile screen page, we are taking advantage of the provider pattern. We reach out to the data related to the user and the methods that are required to sign out through the login provider.

class ProfileScreen extends StatelessWidget {
static const routeName = '/profile-screen';

@override
Widget build(BuildContext context) {
final loginProvider = Provider.of<LoginProvider>(context, listen: false);
final _user = Provider.of<LoginProvider>(context).getUser;
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
backgroundColor: Colors.black45,
),
body: Column(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SafeArea(
child: Row(
children: [
_buildCircleAvatar(_user.profilePhotoUrl),
_userInformationText(_user.displayName, _user.email),
],
),
),
Divider(
color: Colors.black26,
height: 50,
),
],
),
OutlineButton.icon(
textColor: Colors.black54,
onPressed: () {
loginProvider.signOut().then((value) {
loginProvider.revokeAuthorization().then((value) =>
Navigator.of(context)
.pushReplacementNamed(LoginScreen.routeName));
});
},
icon: Icon(Icons.exit_to_app_sharp, color: Colors.black54),
label: Text("Log out"),
)
],
),
);
}
}

Widget _buildCircleAvatar(String photoUrl) {
return Padding(
padding: const EdgeInsets.only(
left: 10,
top: 30,
),
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 3),
shape: BoxShape.circle,
color: Colors.white,
image: DecorationImage(
fit: BoxFit.cover,
image: photoUrl == null
? AssetImage('assets/images/profile_circle_avatar.png')
: NetworkImage(photoUrl),
),
),
),
);
}

Widget _userInformationText(String name, String email) {
return Padding(
padding: const EdgeInsets.only(left: 15.0, top: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: TextStyle(
fontSize: 15.0,
letterSpacing: 1,
fontWeight: FontWeight.w600,
),
),
SizedBox(
height: 3,
),
email == null
? Text('')
: Text(
email,
style: TextStyle(
color: Colors.grey,
fontSize: 12.0,
letterSpacing: 1,
fontWeight: FontWeight.w600,
),
),
],
),
);
}

You can find the source code of the demo app here.

In this article, we have developed a sample application of the HUAWEI Account Kit with Provider pattern in Flutter. I hope this article makes it easier for you to integrate Account Kit into your Flutter projects.

RESOURCES

Account Kit Service