Deep Linking on Flutter using Huawei Push Kit’s Custom Intents - Huawei Developers

In this article I will show the basics of deep linking for Flutter, using Huawei Push Kit Plugin along with uni_links package.
Here are the links for those packages:
https://pub.dev/packages/huawei_push
https://pub.dev/packages/uni_links
Deep Linking
The most basic definition for a deep link is: “A link that sends users to related content on an application”.
Okay but why is this important ?
For improving the User Experience (UX) of course. By utilizing custom uniform resource identifiers (URIs), developers can create funnels in their apps for landing users to the specific content and make the user experience better.
We can validate this with an example: An e-commerce application on your phone has sent you a notification that there will be a discount on stickers. Would you prefer going to the stickers page by tapping the notification or rather navigate by yourself through the enourmous menus like Home > Products > Handmade Products > Stationery & Party Supplies > Stationery Stickers. I am assuming you would choose the first aproach.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Huawei Push Kit allows developers to send push notifications that can include custom intents or actions. This is well suited for our case. So let’s get started for the sake of our users’ experience.
Before we begin, there are prerequisites that need to be completed.
Huawei Developer Account: you must have this account to use the Push Kit Service. Click here to sign up if you don’t have an account already.
HMS Core SDK setup: For using the Push Kit we have to make some configurations on Huawei Developer Console and in our application. Refer to this medium post for installation and if you have any trouble doing so you can also check this post for a more in-depth setup.
The project
The project will be a very simple app that will display information about Huawei Mobile Services. Here is the project’s Github link if you want to follow from there.
Project Setup
As I’ve mentioned before we will use uni_links and Huawei Push Kit plugins in our project. We will also add flutter_webview_plugin into the mix for displaying the website content of the service. So let’s start by adding these to our pubspec.yaml file
Code:
dependencies:
flutter:
sdk: flutter
huawei_push: 4.0.4+300
uni_links: 0.4.0
flutter_webview_plugin: 0.3.11
To listen for intents, uni_links package needs a configuration in the AndroidManifest.xml file. Add an intent filter inside <activity> tag like below.
Code:
<application
<!-- . . .
Other Configurations
. . . -->
<activity/>
<!-- . . .
Other Configurations
. . . -->
<!-- Add the intent filter below.(inside the application and activity tags) -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="app"/>
</intent-filter>
</activity>
</application
Here we have used a scheme called “app”. You can also use your own custom scheme. For more information on this subject refer to this document.
Now that we are done with the installation let’s get started with coding. The project is very simple, you can check the file hierarchy below.
We have two app pages. Home page will show various Huawei Mobile Services and the content page will display the service information inside a webview. We will navigate to this page if a notification with custom intent is tapped.
Main.dart
Code:
import 'package:deep_linking_demo/router.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Deep Linking Demo',
theme: ThemeData(
primarySwatch: Colors.red,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
onGenerateRoute: Router.generateRoute,
initialRoute: '/',
);
}
}
This main.dart file is almost identical with what you get for default except for the named route generation. Here, I have defined the onGenerateRoute and initialRoute properties of the MaterialApp and I have passed Router’s generateRoute method. Let’s look at the router.dart file to see what’s going on.
Router.dart
I used named routes because they contain less boilerplate and easier to use with custom intents. For using the named routes in Flutter we should set the names for our routes and return the corresponding MaterialPageRoute based on the name. Here, I am also getting the arguments needed for the content page and passing to its widget. (We put these arguments when we call the navigator)
Code:
import 'package:deep_linking_demo/screens/content_page.dart';
import 'package:deep_linking_demo/screens/home_page.dart';
import 'package:flutter/material.dart';
class Router {
static const String HomePageRoute = '/';
static const String ContentPageRoute = '/ContentPage';
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case HomePageRoute:
return MaterialPageRoute(builder: (context) => HomePage());
case ContentPageRoute:
final ContentPageArguments args = settings.arguments;
return MaterialPageRoute(
builder: (context) => ContentPage(
serviceName: args.serviceName,
serviceUrl: args.serviceUrl,
),
);
default:
// Error the named route doesn't exist
return MaterialPageRoute(builder: (context) => HomePage());
}
}
}
Hms.dart
This class’ mere purpose is to hold the data for the names and URLs for the HMS services that will be displayed on our home page.
Code:
class HMS {
final String name;
final String url;
final HMSGroup hmsGroup;
const HMS(this.name, this.url, this.hmsGroup);
static const Map<HMSGroup, List<HMS>> HMSMap = {
HMSGroup.AI : _aiServicesList,
HMSGroup.SECURITY : _securityServicesList,
// ...Rest of the mappings
};
static const List<HMS> _aiServicesList = [
HMS('ML Kit','https://developer.huawei.com/consumer/en/hms/huawei-mlkit',HMSGroup.AI),
HMS('HUAWEI HiAI Foundation','https://developer.huawei.com/consumer/en/hiai#Foundation',HMSGroup.AI),
HMS('HUAWEI HiAI Engine','https://developer.huawei.com/consumer/en/hiai#Engine',HMSGroup.AI),
HMS('HUAWEI HiAI Service','https://developer.huawei.com/consumer/en/hiai#Service',HMSGroup.AI)
];
static const List<HMS> _securityServicesList = [
HMS('FIDO','https://developer.huawei.com/consumer/en/hms/huawei-fido',HMSGroup.SECURITY),
HMS('Safety Detect','https://developer.huawei.com/consumer/en/hms/huawei-safetydetectkit/',HMSGroup.SECURITY)
];
// ...Rest of the list definitions
}
enum HMSGroup {
APP_SERVICES,
MEDIA,
GRAPHICS,
AI,
SMART_DEVICE,
SECURITY,
SYSTEM
}
extension HMSGroupExtension on HMSGroup {
String get text {
switch (this) {
case HMSGroup.APP_SERVICES:
return "App Services";
case HMSGroup.MEDIA:
return "Media";
case HMSGroup.GRAPHICS:
return "Graphics";
case HMSGroup.AI:
return "AI";
case HMSGroup.SMART_DEVICE:
return "Smart Device";
case HMSGroup.SECURITY:
return "Security";
case HMSGroup.SYSTEM:
return "System";
default:
return "Other";
}
}
}
I’ve deleted some of the definitions to not bother you with details. Check the github repo for the full code.
Home_page.dart
This is the widget that the most important functions occur so I will split into parts and get into some details for a better explanation.
You can refer to this part for creating your own custom intent navigations for the purpose of deep linking.
Obtaining a push token
For sending push notifications we need to obtain a push token for our device.
Under the state of the widget, define an EventChannel that will listen for the push token and a string variable to hold the token.
Code:
class _HomePageState extends State<HomePage> {
String _token = '';
static const EventChannel TokenEventChannel =
EventChannel(Channel.TOKEN_CHANNEL);
. . .
}
Initialize functions below for obtaining the push token.
Code:
Future<void> initPlatformState() async {
if (!mounted) return;
TokenEventChannel.receiveBroadcastStream()
.listen(_onTokenEvent, onError: _onTokenError);
await Push.getToken();
}
void _onTokenEvent(Object event) {
// This function gets called when we receive the token successfully
setState(() {
_token = event;
});
print('Push Token: ' + _token);
}
void _onTokenError(Object error) {
setState(() {
_token = error;
});
print(_token);
}
Call the initPlatformState function on the widget’s initState method. You should now see your token printed on the debug console.
Code:
@override
void initState() {
super.initState();
initPlatformState();
}
Obtaining a push token is the most crucial part when using the push kit. If you run into any errors while obtaining the token here is checklist that could help:
1. Check your SHA-256 signature and package name on the Huawei Developer Console
2. Make sure the Push Kit is enabled on the console (Manage APIs tab)
3. Whenever you change something on the console, download the agconnect-services.json file again.
Deep linking
We will need two functions to listen for the custom intents: One is for when our app is on foreground (active) and the other one is for when the app is not active and it is opened by an intent.
Code:
Future<Null> initLinkStream() async {
if (!mounted) return;
_sub = getLinksStream().listen((String link) {
print(link);
var uri = Uri.dataFromString(link);
String page = uri.path.split('://')[1];
String serviceName = uri.queryParameters['name'];
String serviceUrl = uri.queryParameters['url'];
Navigator.of(context).pushNamed(
page,
arguments: ContentPageArguments(serviceName, serviceUrl),
); // Navigate to the page from the intent
}, onError: (err) {
print("Error while listening for the link stream: " + err.toString());
});
}
Future<void> initInitialLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
String initialLink = await getInitialLink();
print(initialLink ?? 'NO LINK');
if (initialLink != null) {
print(initialLink);
var uri = Uri.dataFromString(initialLink);
String page = uri.path.split('://')[1];
String serviceName = uri.queryParameters['name'];
String serviceUrl = uri.queryParameters['url'];
try {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Navigator.of(context).pushNamed(
page,
arguments: ContentPageArguments(serviceName, serviceUrl),
); // Navigate to the page from the intent
});
} catch (e) {
Push.showToast(e);
}
}
} on PlatformException {
print('Error: Platform Exception');
}
}
Call these functions on the widgets initState
Code:
@override
void initState() {
super.initState();
initPlatformState();
initInitialLinks();
initLinkStream();
}
Our custom intent inside the push notification looks like this:
Code:
app:///ContentPage?name=Push Kit&url=https://developer.huawei.com/consumer/en/hms/huawei-pushkit
By adding query params, we can utilize the Uri class’ queryParameters method to easily obtain the values we need and not worry about string parsing.
Now for the final part of home_page.dart here is the UI code below.
Code:
Widget serviceButton(HMS service) {
return ListTile(
title: Text(service.name),
onTap: () => Navigator.of(context).pushNamed(
'/ContentPage',
arguments: ContentPageArguments(service.name, service.url),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Huawei Mobile Services"),
centerTitle: true,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height * 0.8,
child: ListView.builder(
itemCount: HMS.HMSMap.length,
itemBuilder: (context, idx) => ExpansionTile(
title: Text(
'${HMS.HMSMap.keys.elementAt(idx).text}',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 25),
),
children: HMS.HMSMap[HMS.HMSMap.keys.elementAt(idx)]
.map((e) => serviceButton(e))
.toList(),
),
),
)
],
),
);
}
Content_page.dart
The last file is content_page.dart. This widget is very simple since it’s only purpose is to display the related service content inside a webview.
Code:
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
class ContentPageArguments {
final String serviceName;
final String serviceUrl;
ContentPageArguments(this.serviceName, this.serviceUrl);
}
class ContentPage extends StatelessWidget {
final String serviceName;
final String serviceUrl;
ContentPage({Key key, @required this.serviceName, @required this.serviceUrl})
: super(key: key) {
assert(serviceName != null);
assert(serviceUrl != null);
}
@override
Widget build(BuildContext context) {
return WebviewScaffold(
url: serviceUrl,
appBar: AppBar(
title: Text(serviceName),
),
withZoom: true,
withLocalStorage: true,
hidden: true,
initialChild: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Center(
child: Text('Loading.....'),
),
SizedBox(
height: 10,
),
Center(child: CircularProgressIndicator())
],
),
),
);
}
}
Sending Push Notifications with Custom Intent
Now for the last part let’s head over to Huawei Developer Console and create a push notification that include a custom intent. Enter the details like in the image below and press “Test Effect” button or submit your push notification from top right corner.
You can find the custom intent uri entered here on the deep linking section of this article
If you press “Test Effect” button console will prompt you to enter the token you obtained earlier.
Enter the push token obtained earlier in the app
Deep Linking while app is on foreground.
Deep linking is working as expected. Kudos to you if you got this far!
Conclusion
Push notifications and deep linking is a must for a mobile application nowadays since their use can boost up user retention and experience if used properly. Huawei Push Kit’s notifications include custom intent and custom action features for the use of deep linking but they aren’t limited with these ones only. If you want to check all the features click here.
I hope this tutorial was helpful for you. If you have any questions regarding this article feel free to ask them on the comment section.
You can also check our other articles about using Huawei Mobile Services on Flutter below.
https://medium.com/huawei-developers/integrating-huawei-analytics-kit-to-flutter-projects-and-sending-events-3dcc4c4f03f
https://medium.com/huawei-developers/using-huawei-map-kit-on-flutter-applications-f83b2a5668bc
https://medium.com/huawei-developers/sending-push-notifications-on-flutter-with-huawei-push-kit-plugin-534787862b4d
Reference
Github demo project: https://github.com/HMS-Core/hms-flutter-plugin/tree/master/flutter-hms-push

Related

Integrating HMS location kit in flutter using method channel

This article is originally from HUAWEI Developer Forum
Forum link: https://forums.developer.huawei.com/forumPortal/en/home​
As Flutter grows, we would get lot of plugins . If your project needs a specific dependency that does not exist on flutter you can define it in native code (IOS and android) and create a communication link between your flutter code and native code.
Contents
· Download Flutter SDK
· Set up flutter in Android studio
· Understanding the structure
· Running the demo project
· Understanding UI Components
· Threading concepts in flutter
· Integrating HMS to a flutter app
· Using hot reload
· Flutter inspector
Download Flutter SDK
Visit the following url to download the latest flutter SDK for your operating system.
https://flutter.dev/docs/get-started/install
Open Android studio and go to Plugin market place and search flutter and download the flutter plugin and restart your ID to show changes to your IDE, which enables us to create and manage a Flutter project.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Set up flutter in Android studio
To create a new Flutter application select File->New Flutter project and follow the steps mentioned below.
Specify the path of your Flutter SDK that you downloaded in your Flutter SDK path as shown below and click next.
Understanding the structure
Pubspec.yaml : Here you will find the app’s dependencies. This includes configuration files, images and other assets. Enter the package name and the version under the dependencies: section of the pubspec.yaml. Run the command flutter packages get to install these packages, enter the package name and version under the dependencies section.
Main.dart : main.dart is the entry point of any flutter application, this is found under lib folder as shown above, this contains all the dart classes. During creation the main.dart file comes with a simple pre written code. Connect your device to the system or start the emulator and run the project to see the result of the sample project.
Adding images in flutter:
Create an assets folder in the directory that contains the pubspec.yaml file and move all your Images to this folder.
Use pub.get to sync all the files and use the images.
All native flutter dependencies are also added to pubspec.yaml file as shown below
Running Demo project
When we create a new flutter project, we get a simple app with floating action button and a counter, if you change text from the sample project above the counter text and click save it is immediately shown on the screen, this feature is called Hot reload and will be used extensively during flutter development.
We will be creating a simple UI with a click event which helps us to send messages to our native code.
UI Components in Flutter
Scaffold
Container
Material app
Stateful and stateless widget
Scaffold: The foundation for most projects on Android are an App bar, a body which holds our app design and a floating action button as highlighted below.
Create a structure for the design remains simple with a scaffold as most of the commonly used components are defined and supported by Scaffold.
As shown in the program below we have the following properties
· Appbar with a title
· Body with a text at the center of the screen
· Floating action button with onPressed click event
Code:
Widget build(BuildContext context){
Return Scaffold(
appBar:AppBar(
title: const Text(‘Sample Code’),
),
Body: Center(child: Text(‘You have pressed the button $_count times.’)),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _count++),
tooltip: ‘Increment Counter’,
child: const Icon(Icons.add),
),
);
}}
Container: allows us to add background padding and design to different elements available on flutter. The diagram shows an example on using Container where we have a child text and color.
MaterialApp: MaterialApp is required when we use Material components and follow material design in your app
Stateful widget: Stateful widgets are mutable and are used when the UI can change dynamically, flutter creates a state object during build process of a stateful widget, this is the place where mutable state for that widget is saved.
Stateless widget: A stateless widget has no internal state to manage. Icon, IconButton, and Text are examples of stateless widgets, which are a subclass of StatelessWidget and are expected to remain static.
Threading concepts in Flutter
Async: function is asynchronous and you might need to wait a bit to get its result.
Await: wait until this function is finished and you will get its return value.
Future: is a type that ‘comes from the future’ and returns value from your asynchronous function. It can complete with success (.then) or with an error(.catchError).
Then ((value){…}): is a callback that’s called when future completes successfully(with a value).
Difference between Const and final in Flutter
Const: it is initialized during the compilation. Whenever you compile the program the value of PI will be initialized and memory will be allocated. It does not matter if you are using this variable or not.
Final: If you use the final variable in your program only then the value is initialized and memory location is allocated. And if you never use the final variable the value will never be initialized in your program.
Let us use the design and threading concepts to create project which updates location on a click event.
Integrating HMS to a Flutter app
Add a channel name in MyHomePage stateful widget, which will be added to our native code for message passing.
Code:
static const Locationplatform = const MethodChannel("samples.flutter.dev/Location");
We will be adding an Icon button and adding a click event to create a class which extends stateful widget as shown
Code:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
static const Locationplatform = const MethodChannel("samples.flutter.dev/Location");
@override
Widget build(BuildContext context) {
}
}
We have a stateful widget which will hold our click event to pass the message to native code using method channel. The program for the design of the button and click event is defined in the overridden method build in MyHomePageState class.
We have a text element which will be placed below the icon and all of this would be at the center of the screen. We define a Scaffold with app bar and body which would eb the important material design components of our app.
We define tooltip and icon button and center align them and create a text at the top of the screen.
Here is the complete main.dart file
Code:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
static const Locationplatform = const MethodChannel("samples.flutter.dev/Location");
@override
Widget build(BuildContext context) {
}
}
Using hot reload
One of the best feature of flutter is hot reload, make changes to your UI and click save while your device is connected to pc and click Ctrl + S on windows and see changes instantly on your screen.
Hot reload helps you see code and user interface changes instantly on your virtual device or mobile device. This is an extremely useful feature for the developer as it saves development time.
To see the changes instantly on your device change the title of your material app as shown above and click ctrl+S on windows and cmd+S on Mac.
It is important to know that any editing that will be performed in gradle or your JAVA/Kotlin files have to be opened for editing in a new window as editing it in the same files could show errors, which cannot be corrected.
After selecting open for editing in android studio as show below, we can follow the mentioned steps to add HMS location kit dependency, the process remains the same as adding any other kit to your project.
Please follow these steps
Creating an AGC Application.
Creating an Android Studio Project.
Generating a signature certificate.
Generating a signature certificate fingerprint.
Configuring the signature certificate fingerprint.
Adding the application package name and save the configuration file.
Configure the Maven address and AGC gradle plug-in.
Configure the signature file in Android Studio.
For more information please check the following link:
https://developer.huawei.com/consumer/en/codelab/HMSLocationKit/index.html#0
Define the Method channel in MainActivity file as shown below and this name should match the channel name in your main.dart file
Code:
private static final String ShowLocationchannel = "samples.flutter.dev/Location";
Extend your MainActivity class with FlutterActivity and override configureFlutterEngine to handle click event.
Code:
@Override
public void configureFlutterEngine(FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), ShowLocationchannel)
.setMethodCallHandler(
(call, result) -> {
if(call.method.equals("ShowLocation"))
{
ShowLocation();
result.success(0);
}
}
);
}
Create a method ShowLocation() and create an Intent to move to LocationActivity class
Code:
private int ShowLocation() {
startActivity(new Intent(MainActivity.this, LocationActivity.class));
return 0;
} Create LocationActivity class that uses FusedLocationProviderClient object to requestLocationUpdates and settingsClient object to call checkLocationSettings method to check device settings.
public class LocationActivity extends AppCompatActivity implements View.OnClickListener {
public static final String TAG = "LocationUpdatesCallback";
// the callback of the request
LocationCallback mLocationCallback;
LocationRequest mLocationRequest;
private FusedLocationProviderClient fusedLocationProviderClient;
private SettingsClient settingsClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_location);
findViewById(R.id.location_requestLocationUpdatesWithCallback).setOnClickListener(this);
findViewById(R.id.location_removeLocationUpdatesWithCallback).setOnClickListener(this);
// create fusedLocationProviderClient
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
// create settingsClient
settingsClient = LocationServices.getSettingsClient(this);
mLocationRequest = new LocationRequest();
// Set the interval for location updates, in milliseconds.
mLocationRequest.setInterval(10000);
// set the priority of the request
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
if (null == mLocationCallback) {
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult != null) {
List<Location> locations = locationResult.getLocations();
if (!locations.isEmpty()) {
for (Location location : locations) {
Toast.makeText(LocationActivity.this,
"onLocationResult location[Longitude,Latitude,Accuracy]:" + location.getLongitude()
+ "," + location.getLatitude() + "," + location.getAccuracy(),Toast.LENGTH_LONG);
}
}
}
}
@Override
public void onLocationAvailability(LocationAvailability locationAvailability) {
if (locationAvailability != null) {
boolean flag = locationAvailability.isLocationAvailable();
Log.i(TAG, "onLocationAvailability isLocationAvailable:" + flag);
}
}
};
}
// check location permisiion
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
Log.i(TAG, "sdk < 28 Q");
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
String[] strings =
{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
ActivityCompat.requestPermissions(this, strings, 1);
}
} else {
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this,
"android.permission.ACCESS_BACKGROUND_LOCATION") != PackageManager.PERMISSION_GRANTED) {
String[] strings = {android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
"android.permission.ACCESS_BACKGROUND_LOCATION"};
ActivityCompat.requestPermissions(this, strings, 2);
}
}
}
private void requestLocationUpdatesWithCallback() {
try {
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
LocationSettingsRequest locationSettingsRequest = builder.build();
// check devices settings before request location updates.
settingsClient.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener(new OnSuccessListener<LocationSettingsResponse>() {
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
Log.i(TAG, "check location settings success");
// request location updates
fusedLocationProviderClient
.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper())
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Log.i(TAG, "requestLocationUpdatesWithCallback onSuccess");
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG,
"requestLocationUpdatesWithCallback onFailure:" + e.getMessage());
}
});
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "checkLocationSetting onFailure:" + e.getMessage());
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
try {
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(LocationActivity.this, 0);
} catch (IntentSender.SendIntentException sie) {
Log.e(TAG, "PendingIntent unable to execute request.");
}
break;
}
}
});
} catch (Exception e) {
Log.e(TAG, "requestLocationUpdatesWithCallback exception:" + e.getMessage());
}
}
@Override
protected void onDestroy() {
// don't need to receive callback
removeLocationUpdatesWithCallback();
super.onDestroy();
}
/**
* remove the request with callback
*/
private void removeLocationUpdatesWithCallback() {
try {
fusedLocationProviderClient.removeLocationUpdates(mLocationCallback)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Log.i(TAG, "removeLocationUpdatesWithCallback onSuccess");
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "removeLocationUpdatesWithCallback onFailure:" + e.getMessage());
}
});
} catch (Exception e) {
Log.e(TAG, "removeLocationUpdatesWithCallback exception:" + e.getMessage());
}
}
@Override
public void onClick(View v) {
try {
switch (v.getId()) {
case R.id.location_requestLocationUpdatesWithCallback:
requestLocationUpdatesWithCallback();
break;
case R.id.location_removeLocationUpdatesWithCallback:
removeLocationUpdatesWithCallback();
break;
default:
break;
}
} catch (Exception e) {
Log.e(TAG, "RequestLocationUpdatesWithCallbackActivity Exception:" + e);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (grantResults.length > 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "onRequestPermissionsResult: apply LOCATION PERMISSION successful");
} else {
Log.i(TAG, "onRequestPermissionsResult: apply LOCATION PERMISSSION failed");
}
}
if (requestCode == 2) {
if (grantResults.length > 2 && grantResults[2] == PackageManager.PERMISSION_GRANTED
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "onRequestPermissionsResult: apply ACCESS_BACKGROUND_LOCATION successful");
} else {
Log.i(TAG, "onRequestPermissionsResult: apply ACCESS_BACKGROUND_LOCATION failed");
}
}
}
}
Code:
<span style="font-family: arial , helvetica , sans-serif;font-size: 18.0px;"><strong>Please add the following permissions to your manifest file to request Location updates</strong></span><strong>
</strong>
Code:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
Flutter inspector
We can use the Flutter inspector to find the nesting of all your widgets making it very easy to edit or find the widget you are looking for, improving design or changes to your UI is extremely simple in flutter using Enable select widget module.
Click select widget module to use he feature as shown below, after you click the select widget module, click on any widget on your screen and you will see changes to your flutter inspector widget tab. The widget selected on your device is highlighted and changes can be made this makes it simpler to find the widget if you a lot of widgets in your widget tree.
Click on the find Icon as shown in the figure to repeat the process again to find a widget.
Conclusion
An existing HMS project can be linked to a flutter screen using a method channel.
This completes the flutter tutorial, please comment like and share.
Stay tuned for more updates

Flutter check HMS/GMS Availability check

More information like this, you can visit HUAWEI Developer Forum​
This guide describes how to write custom platform-specific code. Some platform-specific functionality is available through existing packages
Flutter uses a flexible system that allows you to call platform-specific APIs whether available in Kotlin or Java code on Android, or in Swift or Objective-C code on iOS.
Flutter’s platform-specific API support does not rely on code generation, but rather on a flexible message passing style:
The Flutter portion of the app sends messages to its host, the iOS or Android portion of the app, over a platform channel.
The host listens on the platform channel, and receives the message. It then calls into any number of platform-specific APIs—using the native programming language—and sends a response back to the client, the Flutter portion of the app.
Architectural overview: platform channels
Messages are passed between the client (UI) and host (platform) using platform channels as illustrated in this diagram:
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Messages and responses are passed asynchronously, to ensure the user interface remains responsive.
Step 1: Create a new app project
Start by creating a new app:
In a terminal run: flutter create flutterhmsgmscheck
Step 2: Create the Flutter platform client
The app’s State class holds the current app state. Extend that to hold the current battery state.
First, construct the channel. Use a MethodChannel with a single platform method that returns the battery level.
The client and host sides of a channel are connected through a channel name passed in the channel constructor. All channel names used in a single app must be unique; prefix the channel name with a unique ‘domain prefix’, for example: com.salman.flutter.hmsgmscheck/isHmsGmsAvailable.
Code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HmsGmsCheck extends StatelessWidget {
HmsGmsCheck();
@override
Widget build(BuildContext context) {
return HmsGmsCheckStateful(
title: "HMS/GMS Check",
);
}
}
class HmsGmsCheckStateful extends StatefulWidget {
HmsGmsCheckStateful({Key key, this.title}) : super(key: key);
final String title;
@override
_HmsGmsCheckState createState() => _HmsGmsCheckState();
}
class _HmsGmsCheckState extends State<HmsGmsCheckStateful> {
static const MethodChannel methodChannel =
MethodChannel('com.salman.flutter.hmsallkitsflutter/isHmsGmsAvailable');
bool _isHmsAvailable;
bool _isGmsAvailable;
@override
void initState() {
checkHmsGms();
}
void checkHmsGms() async {
await _isHMS();
await _isGMS();
}
Future<void> _isHMS() async {
bool status;
try {
bool result = await methodChannel.invokeMethod('isHmsAvailable');
status = result;
print('status : ${status.toString()}');
} on PlatformException {
print('Failed to get _isHmsAvailable.');
}
setState(() {
_isHmsAvailable = status;
});
}
Future<void> _isGMS() async {
bool status;
try {
bool result = await methodChannel.invokeMethod('isGmsAvailable');
status = result;
print('status : ${status.toString()}');
} on PlatformException {
print('Failed to get _isGmsAvailable.');
}
setState(() {
_isGmsAvailable = status;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: <Widget>[
new Container(
padding: EdgeInsets.all(20),
child: new Column(
children: <Widget>[
Text(
"HMS Available: $_isHmsAvailable",
style: Theme.of(context).textTheme.headline6,
),
Text(
"GMS Available: $_isGmsAvailable",
style: Theme.of(context).textTheme.headline6,
)
],
),
)
],
));
}
}
Step 3: Update your gradle
Open your gradle in Android Studio and apply huawei repo:
Project-level build.gradle
Code:
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
maven { url 'https://developer.huawei.com/repo/' }
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://developer.huawei.com/repo/' }
}
}
App-level build.gradle
Code:
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "com.huawei.hms:hwid:4.0.0.300"
implementation "com.google.android.gms:play-services-base:17.3.0"
}
Step 4: Add an Android platform-specific implementation
Start by opening the Android host portion of your Flutter app in Android Studio:
Start Android Studio
Select the menu item File > Open…
Navigate to the directory holding your Flutter app, and select the android folder inside it. Click OK.
Open the file MainActivity.kt located in the kotlin folder in the Project view. (Note: If editing with Android Studio 2.3, note that the kotlin folder is shown as if named java.)
Inside the configureFlutterEngine() method, create a MethodChannel and call setMethodCallHandler(). Make sure to use the same channel name as was used on the Flutter client side.
Code:
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.salman.flutter.hmsgmscheck/isHmsGmsAvailable"
var concurrentContext = [email protected]
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
// Note: this method is invoked on the main thread.
if (call.method.equals("isHmsAvailable")) {
result.success(isHmsAvailable());
} else if (call.method.equals("isGmsAvailable")) {
result.success(isGmsAvailable());
} else {
result.notImplemented()
}
}
}
private fun isHmsAvailable(): Boolean {
var isAvailable = false
val context: Context = concurrentContext
if (null != context) {
val result = HuaweiApiAvailability.getInstance().isHuaweiMobileServicesAvailable(context)
isAvailable = ConnectionResult.SUCCESS == result
}
Log.i("MainActivity", "isHmsAvailable: $isAvailable")
return isAvailable
}
private fun isGmsAvailable(): Boolean {
var isAvailable = false
val context: Context = concurrentContext
if (null != context) {
val result: Int = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
isAvailable = com.google.android.gms.common.ConnectionResult.SUCCESS === result
}
Log.i("MainActivity", "isGmsAvailable: $isAvailable")
return isAvailable
}
}
After completing above all steps compile your project you will get the following output.
Conclusion:
With the help of this article we can able to access platform specific native code under our flutter application. For further more details you can check offical flutter platform channels guide.

Sending Push Notifications on Flutter with Huawei Push Kit Plugin

More information like this, you can visit HUAWEI Developer Forum​
Push notifications offer a great way to increase your application’s user engagement and boost your retention rates by sending meaningful messages or by informing your users about your application. These messages can be sent at any time and even if your app is not running at that time.
Huawei Push Kit
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Huawei Push Kit is a messaging service developed by Huawei for developers to send messages to apps on users’ device in real time. Push Kit supports two types of messages: notification messages and data messages, which we will cover both in this tutorial. You can send notifications and data messages to your users from your server using the Push Kit APIs or directly from the AppGallery Push Kit Console.
Recently we released the Huawei Push Kit Plugin for Flutter to make the integration of Push Kit with your Flutter apps easily.
You can find the plugin on from the link below.
Github: https://github.com/HMS-Core/hms-flutter-plugin/tree/master/flutter-hms-push
We will use this plugin throughout the tutorial, so let’s get started.
Configure your project on AppGallery Connect
First of all you should have a Huawei Developer Account to use the Huawei Mobile Services and thus the Huawei Push Kit. I will not get into details about how you create the developer account since it is very straightforward and out of the scope of this tutorial but you can find all the details here.
Let’s start by creating a new project on Huawei Developer Console. If you already created a project before you can skip this step. Just make sure you have set the Data Storage Location and entered the SHA-256 fingerprint.
Create an app and enable Push Kit
1. Go to Huawei Developer Console and select Huawei Push under Development section. Press the “New App” button on the top right corner to create a new app. You can refer to this article if you came across any problem.
2. You’ll be forwarded to Push Kit page after the creation of your app, you can also navigate here from the side menu. Enable the Push Kit service by clicking “Enable Now”.
3. After enabling Push Kit, console will prompt you to enter the package name and it will redirect you to Project Settings. You can manually enter the package name or you can upload your package to set it automatically. I choose the option to enter manually which is in my case “com.example.flutter_push_kit_tutorial”. You can find this from the first lines of your AndroidManifest.xml file under <your_flutter_project>/android/app/src/AndroidManifest.xml
4. On the Project Settings page set the Data Storage Location as Germany. This is needed to store and send out our push messages.
Generate and Configure Signing Certificate Fingerprint
A signing certificate fingerprint is needed to verify the authenticity of our app when it attempts to access the HMS Push Kit through the HMS Core SDK. So before we able to use the push service, we must generate a signing certificate and configure it in AppGallery Connect. We will also use this certificate later in the Flutter project configuration.
Before generating the signing certificate we must have the JDK installed. This is installed by default with Android Studio. Go to the Java/JDK installation’s bin directory in your pc and open cmd or powershell to run the following command:
keytool-genkey-keystore <keystore-file> -storepass <keystore-pass> -alias <key-alias> -keypass <key-pass> -keysize 2048 -keyalg RSA -validity <validity-period>
The fields that should be filled in the command are as follows:
· <keystore-file> is the path to the app’s signature file. File extension must be .jks or .keystore. For example; C:\key.jks
· <keystore-pass> is the password of your keystore.
· <key-alias> is the alias name of key that will be stored in your keystore.
· <key-pass> is the password of your key.
· <validity-period> Amount of days the key will be valid with this keystore.
Example command:
Code:
keytool -genkey -keystore C:\Users\Username\key.jks -storepass 123456 -alias pushkitkey -keypass 123456 -keysize 2048 -keyalg RSA -validity 36500
Note that you can run this command anywhere if you configured your system environment variables to include JDK folders.
Now that we generated our certificate we need to obtain the SHA-256 fingerprint and add it to the project settings on the AppGallery Connect. To obtain the fingerprints run the command below at the same directory you generated the keystore (Java/JDK bin folder where keytool located)
Code:
keytool -list -v -keystore C:\Users\Username\key.jks
Obtaining SHA-256 Fingerprint from powershell
Copy and paste the SHA-256 fingerprint you obtained to SHA-256 field on the project settings page. Make sure this value has set before you move on. After everything is done, your project settings should look like below.
Completed AppGallery Connect Configuration
Integrate HMS to your Flutter project
Now that we are done with the configuration on the AppGallery Connect let’s move to Flutter part to finish the setup.
Add the Huawei Push Kit Plugin dependency to the project’s pubspec.yaml file and run flutter pub get to load the plugin.
Code:
dependencies:
flutter:
sdk: flutter
huawei_push: 4.0.4+300
For integration with the Huawei Push Kit as well as other Huawei Mobile Services we must download agconnect-services.json file from project settings>app information (where we entered the fingerprint before) and add it to the folder :<your_flutter_project>/android/app
Your project should look like this after adding the services
Add the lines below to the project level build.gradle file
(your_flutter_project/android/build.gradle)
Code:
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
maven { url 'https://developer.huawei.com/repo/' } // Add this line
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.huawei.agconnect:agcp:1.3.1.300' // Add this line
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://developer.huawei.com/repo/' } // Add this line
}
}
/* other configurations/*
Create a key.properties file inside the android folder for gradle to read the keystore values that we generated before.
Code:
storePassword=<your_keystore_pass>
keyPassword=<your_key_pass>
keyAlias=<alias_you_entered_before>
storeFile=<path_to_keystore.jks>
On the first line of app level build.gradle located on:
your_flutter _project/android/app/build.gradle
add this line to read the values of key.properties file.
Code:
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
Increase your minSdkVersion to 17 and make the following changes on the same app level build.gradle file
Code:
android {
/*
Other configurations
…
*/
defaultConfig {
// The package name below and on AppGallery Connect should be the same
applicationId "com.example.flutter_push_kit_tutorial"
minSdkVersion 17 // Increase this to 17
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
// Add this part
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
// Edit here
buildTypes {
debug {
signingConfig signingConfigs.release
}
release {
signingConfig signingConfigs.release
}
}
}
Add this line to the end of the same file
Code:
apply plugin: 'com.huawei.agconnect' // Add this line (this needs to be at the bottom of your build.gradle)
Important Note
If you are planning to get a release apk you also need to configure proguard rules to prevent HMS Core SDK from being obfuscated. As stated in this documentation Google’s R8 compiler automatically does shrinking and obfuscating on release builds to reduce the size of your app.
Add following lines to proguard-rules.pro file, create the file inside the android folder if you don’t have it already. You may need further configuration on build.gradle, please refer to the documentation above or this stackoverflow issue.
Code:
## Flutter wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
-dontwarn io.flutter.embedding.**
## HMS Core SDK
-ignorewarnings
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-keep class com.hianalytics.android.**{*;}
-keep class com.huawei.updatesdk.**{*;}
-keep class com.huawei.hms.**{*;}
Testing Push Notification
Now that we are ready to use the Push Kit in our Flutter project, let’s get a token for testing the push notification and then we can move with a little bit more complex example.
To receive the token we must initialize an EventChannel and listen the changes from the stream. I’ve initialized the channel and requested a token in the initState of the HomeScreen widget.
Code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:huawei_push/push.dart';
import 'package:huawei_push/constants/channel.dart' as Channel;
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String _token = '';
static const EventChannel TokenEventChannel =
EventChannel(Channel.TOKEN_CHANNEL);
@override
void initState() {
super.initState();
initPlatformState();
getToken();
}
Future<void> initPlatformState() async {
if (!mounted) return;
TokenEventChannel.receiveBroadcastStream()
.listen(_onTokenEvent, onError: _onTokenError);
}
void _onTokenEvent(Object event) {
// This function gets called when we receive the token successfully
setState(() {
_token = event;
});
print('Push Token: ' + _token);
Push.showToast(event);
}
void _onTokenError(Object error) {
setState(() {
_token = error;
});
Push.showToast(error);
}
void getToken() async {
await Push.getToken();
}
@override
Widget build(BuildContext context) {
return Scaffold(
// Rest of the widget...
);
}
}
We got the push token after running our app and now we can test the push notification by sending one from the Push Kit Console. Navigate to Push Kit > Add Notification and complete the required fields, you should enter the token we got earlier to the specified device in the push scope part. You can test the notification immediately by pressing test effect button or you can submit your notification.
Sending Push Notification from Huawei Push Kit Console
We have received our first notification
Subscribing to a topic and receiving data messages
Topics are like separate messaging channels that we can send notifications and data messages to. Devices, subscribe to these topics for receiving messages about that subject. For example, users of a weather forecast app can subscribe to a topic that sends notifications about the best weather for exterminating pests. You can check here for more use cases.
Data Messages are customized messages that their content is defined by you and parsed by your application. After receiving these messages, the system transfers it to the app instead of directly displaying the message. App then can parse the message and can trigger some action.
In my example I will define a ‘coupon’ topic that users can subscribe to receive coupons. And then I will send a data message which includes the coupon from the Push Kit Console. (Note that this can also be done by using the Push Kit API)
Let’s define the Coupon class to convert our data message to a coupon object
Code:
class Coupon {
String title;
String body;
String couponCode;
Coupon({this.title, this.body, this.couponCode});
Coupon.fromJson(Map<String, dynamic> json) {
title = json['title'];
body = json['body'];
couponCode = json['couponCode'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['title'] = this.title;
data['body'] = this.body;
data['couponCode'] = this.couponCode;
return data;
}
}
In home_screen.dart I’ve added a data message event channel just like we did in the token part to receive the messages. And then I’ve added a subscription function and a method to show a dialog if we receive a coupon.
Code:
/*
Imports...
*/
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool _subscribed = false;
static const EventChannel DataMessageEventChannel =
EventChannel(Channel.DATA_MESSAGE_CHANNEL);
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
if (!mounted) return;
DataMessageEventChannel.receiveBroadcastStream()
.listen(_onDataMessageEvent, onError: _onDataMessageError);
}
void _onDataMessageEvent(Object event) {
Map<String, dynamic> dataObj = json.decode(event);
if (dataObj['type'] == 'coupon') {
Coupon coupon = Coupon.fromJson(dataObj);
showCouponDialog(coupon);
} else {
print('Unsupported Data Message Type');
}
Push.showToast(event);
}
void _onDataMessageError(Object error) {
Push.showToast(error);
}
void subscribeToCoupons() async {
setState(() {
_subscribed = true;
});
String topic = 'coupon';
dynamic result = await Push.subscribe(topic);
Push.showToast(result);
}
showCouponDialog(Coupon coupon) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Container(
child: Text(
coupon.title.toUpperCase(),
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.green,
fontSize: 25,
),
)),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
content: Container(
height: 200,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(coupon.body),
SizedBox(
height: 10,
),
Text(
coupon.couponCode,
style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
),
MaterialButton(
color: Colors.green,
child: Text(
'Claim Now',
style: TextStyle(color: Colors.white),
),
onPressed: () => Navigator.pop(context),
)
],
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('HMS Push Kit Example'),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text('Subscribe to coupon topic to get free coupons'),
SizedBox(
height: 20,
),
OutlineButton(
onPressed: _subscribed ? null : () => subscribeToCoupons(),
child: Text('Subscribe Now'),
borderSide: BorderSide(color: Colors.green),
textColor: Colors.green,
),
],
),
),
);
}
}
Our app is shown below, let’s subscribe to coupon topic by pressing the button.
Now on Huawei Push Kit Console, create a data message like the image below and send it to the coupon topic subscribers.
Configuring the data message from the Push Kit Console
After sending the data message to our coupon subscribers we should see the coupon dialog with the message we have sent.
It must be your lucky day.
I am leaving the project’s github link in case you want to check it from there or try this example by yourself:
https://github.com/atavci/FlutterPushKitTutorial
Conclusion
Now you know how to use the Huawei Push Kit for your Flutter Projects. You can use this information to connect your existing users or add extra functionality that will attract even more users. Like every awesome feature, notifications should be handled with extra care; since nobody likes to get bombarded with them.
I leave some references for further reading, you can also ask questions on the comments section, I would happily answer them.
Have a great day and successful builds!
From:
https://medium.com/huawei-developers/sending-push-notifications-on-flutter-with-huawei-push-kit-plugin-534787862b4d

Using Huawei Cloud Functions as Chatbot Service in Flutter ChatBotApp Part-2

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Introduction
In this article, we will learn how to use Huawei Cloud Functions service as Chatbot service in ChatBotApp in flutter. Cloud Functions enables serverless computing.
It provides the Function as a Service (FaaS) capabilities to simplify app development and O&M by splitting service logic into functions and offers the Cloud Functions SDK that works with Cloud DB and Cloud Storage so that your app functions can be implemented more easily. Cloud Functions automatically scales in or out functions based on actual traffic, freeing you from server resource management and helping you reduce costs.
Key Functions
Key Concepts
How the Service Works
To use Cloud Functions, you need to develop cloud functions that can implement certain service functions in AppGallery Connect and add triggers for them, for example, HTTP triggers for HTTP requests, and Cloud DB triggers for data deletion or insertion requests after Cloud DB is integrated. After your app that integrates the Cloud Functions SDK meets conditions of specific function triggers, your app can call the cloud functions, which greatly facilitates service function building.
Platform Support
Development Overview
You need to install Flutter and Dart plugin in IDE and I assume that you have prior knowledge about the Flutter and Dart.
Hardware Requirements
A computer (desktop or laptop) running Windows 10.
Android phone (with the USB cable), which is used for debugging.
Software Requirements
Java JDK 1.7 or later.
Android studio software or Visual Studio or Code installed.
HMS Core (APK) 4.X or later.
Integration process
Step 1: Create Flutter project.
Step 2: Add the App level gradle dependencies. Choose inside project Android > app > build.gradle.
[/B][/B]
apply plugin: 'com.android.application'
apply plugin: 'com.huawei.agconnect'
[B][B]
Root level gradle dependencies
maven {url 'https://developer.huawei.com/repo/'}
classpath 'com.huawei.agconnect:agcp:1.5.2.300'
Step 3: Add the below permissions in Android Manifest file.
<uses-permission android:name="android.permission.INTERNET" />
Step 4: Add downloaded file into parent directory of the project. Declare plugin path in pubspec.yaml file under dependencies.
Add path location for asset image.
Prevoius article
Using Huawei Cloud Functions as Chatbot Service in Flutter ChatBotApp Part-1
Let's start coding
main.dart
[/B]
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ChatBotService',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'ChatBotService'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isLoggedIn = false;
String str = 'Login required';
final HMSAnalytics _hmsAnalytics = new HMSAnalytics();
List<String> gridItems = ['Email Service', 'Call Center', 'FAQ', 'Chat Now'];
@override
void initState() {
_enableLog();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child:
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Visibility(
visible: true,
child: Card(
child: Padding(
padding: EdgeInsets.all(20),
child: Text(
str,
style: const TextStyle(color: Colors.teal, fontSize: 22),
),
),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (!isLoggedIn) {
setState(() {
isLoggedIn = true;
signInWithHuaweiID();
});
print('$isLoggedIn');
} else {
setState(() {
isLoggedIn = false;
signOutWithID();
});
print('$isLoggedIn');
}
},
tooltip: 'Login/Logout',
child: isLoggedIn ? const Icon(Icons.logout) : const Icon(Icons.login),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void signInWithHuaweiID() async {
try {
// The sign-in is successful, and the user's ID information and authorization code are obtained.
Future<AuthAccount> account = AccountAuthService.signIn();
account.then(
(value) => setLoginSuccess(value),
);
} on Exception catch (e) {
print(e.toString());
}
}
Future<void> _enableLog() async {
_hmsAnalytics.setUserId("ChatBotServiceApp");
await _hmsAnalytics.enableLog();
}
void setLoginSuccess(AuthAccount value) {
setState(() {
str = 'Welcome ' + value.displayName.toString();
});
showToast(value.displayName.toString());
print('Login Success');
}
Future<void> signOutWithID() async {
try {
final bool result = await AccountAuthService.signOut();
if (result) {
setState(() {
str = 'Login required';
showToast('You are logged out.');
});
}
} on Exception catch (e) {
print(e.toString());
}
}
Future<void> showToast(String name) async {
Fluttertoast.showToast(
msg: "$name",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.lightBlue,
textColor: Colors.white,
fontSize: 16.0);
}
}
[B]
ChatPage.dart
[/B][/B]
class ChatPage extends StatefulWidget {
const ChatPage({Key? key}) : super(key: key);
@override
_ChatPageState createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
List<types.Message> _messages = [];
final _user = const types.User(id: '06c33e8b-e835-4736-80f4-63f44b66666c');
final _bot = const types.User(id: '06c33e8b-e835-4736-80f4-63f54b66666c');
void _addMessage(types.Message message) {
setState(() {
_messages.insert(0, message);
});
}
void _handleSendPressed(types.PartialText message) {
final textMessage = types.TextMessage(
author: _user,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: const Uuid().v4(),
text: message.text,
);
_addMessage(textMessage);
callCloudFunction2(message.text);
}
void _loadMessages() async {
final response = await rootBundle.loadString('assets/messages.json');
final messages = (jsonDecode(response) as List)
.map((e) => types.Message.fromJson(e as Map<String, dynamic>))
.toList();
setState(() {
_messages = messages;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Chat(
messages: _messages,
onAttachmentPressed: null,
onMessageTap: null,
onPreviewDataFetched: null,
onSendPressed: _handleSendPressed,
user: _user,
),
);
}
Future<void> callCloudFunction2(String msg) async {
try {
RequestData data = RequestData(msg);
List<Map<String, Object>> params = <Map<String, Object>>[data.toMap()];
var input = data.toMap();
FunctionCallable functionCallable =
FunctionCallable('test-funnel-\$latest');
FunctionResult functionResult = await functionCallable.call(input);
print("Input " + input.toString());
var result = functionResult.getValue();
final textMessage = types.TextMessage(
author: _bot,
createdAt: DateTime.now().millisecondsSinceEpoch,
id: const Uuid().v4(),
text: jsonDecode(result)['response'].toString(),
);
_addMessage(textMessage);
} on PlatformException catch (e) {
print(e.message);
}
}
}
[B][B]
handler.js
[/B][/B][/B]
let myHandler = function(event, context, callback, logger) {
try {
var _body = JSON.parse(event.body);
var reqData = _body.message;
var test = '';
if(reqData == '1'){
test = "Thank you for choosing, you will get callback in 10 min.";
}else if(reqData == '2'){
test = "Please click on the link https://feedback.com/myfeedback";
}else if(reqData == '3'){
test = "Please click on the link https://huawei.com/faq";
}
else if(reqData == 'Hi'){
test = " Welcome to ChatBot Service.";
}else{
test = "Enter 1. For call back. 2. For send feedback. 3. For FAQ ";
}
let res = new context.HTTPResponse({"response": test}, {
"res-type": "simple example",
"faas-content-type": "json"
}, "application/json", "200");
callback(res);
} catch (error) {
let res = new context.HTTPResponse({"response": error}, {
"res-type": "simple example",
"faas-content-type": "json"
}, "application/json", "300");
callback(res);
}
};
module.exports.myHandler = myHandler;
[B][B][B]
Result
Tricks and Tips
Makes sure that agconnect-services.json file added.
Make sure dependencies are added yaml file.
Run flutter pug get after adding dependencies.
Make sure that service is enabled in agc.
Makes sure images are defined in yaml file.
Conclusion
In this article, we have learnt how to integrate Huawei Account kit, analytics kit and ChatBot function using Cloud Functions in flutter ChatBotApp. Once Account kit integrated, users can login quickly and conveniently sign in to apps with their Huawei IDs after granting initial access permission.
Thank you so much for reading. I hope this article helps you to understand the integration of Huawei Account kit, Analytics kit and Huawei Cloud Functions in flutter ChatBotApp.
Reference
Cloud Functions
Training Videos
Checkout in forum

Beginner: Integration of Huawei Push Notification with android Work Manager in Paralysis Glove IoT application and Testing with Postman - Part 2

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Introduction​
If you are new to series of this article, follow the below article.
Beginner: Integration of Huawei Account kit in Paralysis Glove IoT application - Part 1
In this article, we will learn about Paralysis Glove application and also we will learn integration of the Huawei Push kit in Paralysis Glove IoT application. Here it will cover how exactly push notification works, and benefits of push notification and also we can integrate Huawei Push kit in the smart gloves application. And also we will learn how to test the Push notification from the Postman.
Content​
What is Push Notification?
Advantages of Push notification
Type of notification
Huawei Push notification
Message types.
Integration of push kit
Testing push kit from Postman
In the current mobile world 90% of the applications integrated the push notification. Every application integrate the push notification to engage the users. And also to make better marketing. Every ecommerce application shared the details about offers, discounts, price drops for particular product, the delivery status through the push notification.
First let us understand what push notification is.
Push notification are basically alerts to users from mobile application. Users receives in real-time. Developers can send notification at any time and any day.
Advantages of Push Notification.
User retention: In the world of mobile apps, user retention is a key indicator of how well your app is performing in the market. This metric lets you to see how many users who have downloaded your app and used it once come back to use the app again.
Increase engagement: The power of Huawei push notification is to make user engage by sending some cool notification to users. Notification provides the interaction with application, by this user can spend more time with the application.
Push Notification types
Reminder Notification: Its reminder notification for example recharge reminder, meeting time reminder, appointment reminder etc.
Alert Notification: this type of notification alerts to user when something happens in application which depends upon user. Example when someone sends message, comment on pic.
Promotional notification: these are the promotional notification when application offers something to user example discount, sales date, some weekend sales etc.
Purchas notification: These are valuable notifications and have to do with purchases users make within your app. It can contain information like order confirmation, order status, order updates, tracking, and receipts.
Survey notification: these are feedback or survey notification when application wants to get the feedback or survey at that moment these kind of notification will be used.
Huawei Push Kit is a messaging service provided for you to establish a cloud-to-device messaging channel. By integrating Push Kit, you can send messages to your apps on user devices in real time. This helps you to maintain closer ties with users and increases user awareness of and engagement with your apps.
Push Kit consists of two parts:
Message push from the cloud to the device: enables you to send data and messages to your apps on user devices.
Message display on devices: provides various display styles, such as the notification panel, home screen banner, and lock screen on user devices.
Huawei has 2 types of Messages.
1. Notification Message
2. Data Message
Notification Message: A notification message is directly sent by Push Kit and displayed in the notification panel on the user device, not requiring your app to be running in the background. The user can tap the notification message to trigger the corresponding action such as opening your app or opening a web page. You can tailor the display styles and reminder modes to fit user needs, which will greatly improve the daily active users (DAU) of your app. The common application scenarios of the notification message include subscription, travel reminder, and account status.
Batch message: a message sent by an app in batches to users who will obtain the same content, which can improve user experience and stimulate user interaction with the app.
Personalized message: a message generated based on a unified message template and sent by an app to an audience. The unified message template contains placeholders, which will be replaced based on the settings and preferences of specific users.
Point-to-point message: a message sent by an app to a user when the user takes a specific action.
Instant message: An instant message is a point-to-point or group chatting message (or private message) between users.
Data Message: Data messages are processed by your app on user devices. After a device receives a message containing data or instructions from the cloud, the device passes the message to the app instead of directly displaying it. The app then parses the message and triggers the required action (for example, going to a web page or an app page). For such a message, you can also customize display styles for higher efficiency.
Push Kit cannot guarantee a high data message delivery rate, because it may be affected by Android system restrictions and whether the app is running in the background. The common application scenarios of the data message include the VoIP call, voice broadcast, and interaction with friends.
Prerequisite​
AppGallery Account
Android Studio 3.X
SDK Platform 19 or later
Gradle 4.6 or later
HMS Core (APK) 4.0.0.300 or later
Huawei Phone EMUI 5.0 or later
Non-Huawei Phone Android 5.1 or later
Service integration on AppGallery​
1. We need to register as a developer account in AppGallery Connect.
2. Create an app by referring to Creating a Project and Creating an App in the Project.
3. Set the data storage location based on the current location.
4. Enabling Push Kit Service on AppGallery.
5. Generating a Signing Certificate Fingerprint.
6. Configuring the Signing Certificate Fingerprint.
7. Get your agconnect-services.json file to the app root directory.
Client development
1. Create android project in android studio IDE.
2. Add the maven URL inside the repositories of buildscript and allprojects respectively (project level build.gradle file).
Code:
maven { url 'https://developer.huawei.com/repo/' }
3. Add the classpath inside the dependency section of the project level build.gradle file.
Code:
classpath 'com.huawei.agconnect:agcp:1.5.2.300'
4. Add the plugin in the app-level build.gradle file.
Code:
apply plugin: 'com.huawei.agconnect'
5. Add the below library in the app-level build.gradle file dependencies section.
Code:
implementation 'com.huawei.hms:push:6.3.0.302'
6. Add all the below permission in the AndroidManifest.xml.
XML:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<manifest ...>
...
<application ...>
<service android:name=".push.PushNotificationHmsMessageService" android:exported="false">
<intent-filter>
<action android:name="com.huawei.push.action.MESSAGING_EVENT"/>
</intent-filter>
</service>
</application>
...
</manifest>
7. Sync the project.
Getting Push token in android.
Java:
private fun getPushToken() {
object : Thread() {
override fun run() {
try {
// Obtain the app ID from the agconnect-services.json file.
val appId = "your APP_ID"
// Set tokenScope to HCM.
val tokenScope = "HCM"
val token = HmsInstanceId.getInstance([email protected]).getToken(appId, tokenScope)
Log.i(TAG, "get token:$token")
// Check whether the token is null.
if (!TextUtils.isEmpty(token)) {
sendRegTokenToServer(token)
}
} catch (e: ApiException) {
Log.e(TAG, "get token failed, $e")
}
}
}.start()
}
private fun sendRegTokenToServer(token: String?) {
Log.i(TAG, "sending token to server. token:$token")
}
Whenever new token is generated onNewToken() will be called and you need to send the latest token to backend or server.
Java:
// When new token is generated below method gives the new token
override fun onNewToken(token: String?, bundle: Bundle?) {
// Obtain a push token.
Log.i(TAG, "have received refresh token:$token")
// Check whether the token is null.
if (!token.isNullOrEmpty()) {
refreshedTokenToServer(token)
}
}
private fun refreshedTokenToServer(token: String) {
Log.i(TAG, "sending token to server. token:$token")
}
When push notification is triggered from AGC console or Postman below method will be called. You need to generate notification when onMessageReceived() called.
Java:
//When push notification is triggered from AGC console or Postman below method will be called
override fun onMessageReceived(message: RemoteMessage?) {
Log.i(TAG, "onMessageReceived is called")
// Check whether the message is empty.
if (message == null)
{
Log.e(TAG, "Received message entity is null!")
return
}
// Obtain the message content.
Log.i(TAG, """getData: ${message.data}
getFrom: ${message.from}
getTo: ${message.to}
getMessageId: ${message.messageId}
getSentTime: ${message.sentTime}
getDataMap: ${message.dataOfMap}
getMessageType: ${message.messageType}
getTtl: ${message.ttl}
getToken: ${message.token}""".trimIndent())
val judgeWhetherIn10s = false
// If the message is not processed within 10 seconds, create a job to process it.
if (judgeWhetherIn10s) {
startWorkManagerJob(message)
} else {
// Process the message within 10 seconds.
processWithin10s(message)
}
}
private fun startWorkManagerJob(message: RemoteMessage?) {
Log.d(TAG, "Start new Job processing.")
}
private fun processWithin10s(message: RemoteMessage?) {
Log.d(TAG, "Processing now.")
}
Now let’s learn the coding part.
Step 1: Create Notification.
NotificationUtils.kt
Java:
package com.huawei.paralysisglove.push
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import kotlin.random.Random
import android.provider.Settings
import com.huawei.paralysisglove.R
import com.huawei.paralysisglove.ui.activities.SplashActivity
class NotificationUtil(private val context: Context) {
fun showNotification(title: String, message: String) {
val intent = Intent(context, SplashActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val pendingIntent = PendingIntent.getActivity(
context, 0, intent,
PendingIntent.FLAG_ONE_SHOT
)
val channelId = context.getString(R.string.default_notification_channel_id)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(context, channelId)
.setColor(ContextCompat.getColor(context, android.R.color.holo_red_dark))
.setSmallIcon(R.drawable.glove)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.setStyle(
NotificationCompat.BigTextStyle()
.bigText(message)
)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent)
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"Default Channel",
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(Random.nextInt(), notificationBuilder.build())
}
fun isTimeAutomatic(context: Context): Boolean {
return Settings.Global.getInt(
context.contentResolver,
Settings.Global.AUTO_TIME,
0
) == 1;
}
}
Step 2: Create Worker manager to process data and notification in background. If received data need to be processed and if it takes more than 10 seconds, then create work manager else directly show the notification.
SchedulerWorker.kt
Java:
package com.huawei.paralysisglove.push
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
class ScheduledWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
Log.d(TAG, "Work START")
// Get Notification Data
val title = inputData.getString(NOTIFICATION_TITLE)
val message = inputData.getString(NOTIFICATION_MESSAGE)
// Show Notification
NotificationUtil(applicationContext).showNotification(title!!, message!!)
// TODO Do your other Background Processing
Log.d(TAG, "Work DONE")
// Return result
return Result.success()
}
companion object {
private const val TAG = "ScheduledWorker"
const val NOTIFICATION_TITLE = "notification_title"
const val NOTIFICATION_MESSAGE = "notification_message"
}
}
Step 3: Create broadcast receiver.
NotificationBroadcastReceiver.kt
Java:
package com.huawei.paralysisglove.push
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.huawei.paralysisglove.push.ScheduledWorker.Companion.NOTIFICATION_MESSAGE
import com.huawei.paralysisglove.push.ScheduledWorker.Companion.NOTIFICATION_TITLE
class NotificationBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
val title = it.getStringExtra(NOTIFICATION_TITLE)
val message = it.getStringExtra(NOTIFICATION_MESSAGE)
// Create Notification Data
val notificationData = Data.Builder()
.putString(NOTIFICATION_TITLE, title)
.putString(NOTIFICATION_MESSAGE, message)
.build()
// Init Worker
val work = OneTimeWorkRequest.Builder(ScheduledWorker::class.java)
.setInputData(notificationData)
.build()
// Start Worker
WorkManager.getInstance().beginWith(work).enqueue()
Log.d(javaClass.name, "WorkManager is Enqueued.")
}
}
}
Step 4: Add NotificationBroadcastReceiver in the AndroidManifest.xml
XML:
<receiver android:name=".push.NotificationBroadcastReceiver" />
Step 5: Create Huawei PushNotificationHmsMessageService.kt
Java:
package com.huawei.paralysisglove.push
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import com.huawei.hms.push.HmsMessageService
import com.huawei.hms.push.RemoteMessage
import android.text.TextUtils
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.google.gson.Gson
import com.huawei.hms.common.ApiException
import com.huawei.hms.aaid.HmsInstanceId
import com.huawei.agconnect.config.AGConnectServicesConfig
import com.huawei.paralysisglove.push.ScheduledWorker.Companion.NOTIFICATION_MESSAGE
import com.huawei.paralysisglove.push.ScheduledWorker.Companion.NOTIFICATION_TITLE
import com.huawei.paralysisglove.push.model.PushModel
import org.json.JSONException
import java.text.SimpleDateFormat
import java.util.*
class PushNotificationHmsMessageService : HmsMessageService() {
override fun onMessageReceived(message: RemoteMessage?) {
Log.i(TAG, "onMessageReceived is called")
if (message == null) {
Log.e(TAG, "Received message entity is null!")
return
}
Log.i(
TAG, """get Data: ${message.data} getFrom: ${message.from}
getTo: ${message.to}
getMessageId: ${message.messageId}
getSendTime: ${message.sentTime}
getDataMap: ${message.dataOfMap}
getMessageType: ${message.messageType}
getTtl: ${message.ttl}
getToken: ${message.token}"""
)
message.data.isNotEmpty().let { it ->
if (it) {
Log.d(TAG, "Message data payload: ${message.data}")
try {
val pushModel: PushModel = Gson().fromJson(message.data, PushModel::class.java)
//val jsonData = JSONObject(message.data)
// Get Message details
val title = pushModel.title
val content = pushModel.message
// Check whether notification is scheduled or not
val isScheduled = pushModel.isScheduled
isScheduled.let {
if (it) {
// Check that 'Automatic Date and Time' settings are turned ON.
// If it's not turned on, Return
val notificationUtil = NotificationUtil(this)
if (!notificationUtil.isTimeAutomatic(applicationContext)) {
Log.d(TAG, "`Automatic Date and Time` is not enabled")
return
}
// This is Scheduled Notification, Schedule it
val scheduledTime = pushModel.scheduledTime
scheduleAlarm(scheduledTime, title, content)
} else {
// This is not scheduled notification, show it now
// Create Notification Data
var body =
"You have reached from " + pushModel.data.fromLocation + " to " + pushModel.data.toLocation
val notificationData = Data.Builder()
.putString(NOTIFICATION_TITLE, title)
.putString(NOTIFICATION_MESSAGE, body)
.build()
// Init Worker
val work = OneTimeWorkRequest.Builder(ScheduledWorker::class.java)
.setInputData(notificationData)
.build()
// Start Worker
WorkManager.getInstance(this).beginWith(work).enqueue()
Log.d(javaClass.name, "WorkManager is Enqueued.")
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
} else {
val notificationData = Data.Builder()
.putString(NOTIFICATION_TITLE, message.notification.title)
.putString(NOTIFICATION_MESSAGE, message.notification.body)
.build()
// Init Worker
val work = OneTimeWorkRequest.Builder(ScheduledWorker::class.java)
.setInputData(notificationData)
.build()
// Start Worker
WorkManager.getInstance(this).beginWith(work).enqueue()
}
}
}
private fun scheduleAlarm(
scheduledTimeString: String?,
title: String?,
message: String?
) {
val alarmMgr = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmIntent =
Intent(applicationContext, NotificationBroadcastReceiver::class.java).let { intent ->
intent.putExtra(NOTIFICATION_TITLE, title)
intent.putExtra(NOTIFICATION_MESSAGE, message)
PendingIntent.getBroadcast(applicationContext, 0, intent, 0)
}
// Parse Schedule time
val scheduledTime = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
.parse(scheduledTimeString!!)
scheduledTime?.let {
// With set(), it'll set non repeating one time alarm.
alarmMgr.set(
AlarmManager.RTC_WAKEUP,
it.time,
alarmIntent
)
}
}
override fun onNewToken(token: String) {
Log.i(TAG, "received refresh token:$token")
if (!TextUtils.isEmpty(token)) {
refreshedTokenToServer(token)
}
}
// If the version of the Push SDK you integrated is 5.0.4.302 or later, you also need to override the method.
override fun onNewToken(token: String, bundle: Bundle?) {
Log.i(TAG, "have received refresh token $token")
if (!TextUtils.isEmpty(token)) {
refreshedTokenToServer(token)
}
}
private fun refreshedTokenToServer(token: String) {
Log.i(TAG, "sending token to server. token:$token")
}
private fun deleteToken() {
// Create a thread.
object : Thread() {
override fun run() {
try {
// Obtain the app ID from the agconnect-service.json file.
val appId =
AGConnectServicesConfig.fromContext([email protected])
.getString("client/app_id")
// Set tokenScope to HCM.
val tokenScope = "HCM"
// Delete the token.
HmsInstanceId.getInstance([email protected])
.deleteToken(appId, tokenScope)
Log.i(TAG, "token deleted successfully")
} catch (e: ApiException) {
Log.e(TAG, "deleteToken failed.$e")
}
}
}.start()
}
/**
* Persist token to third-party servers.
*
* Modify this method to associate the user's FCM registration token with any server-side account
* maintained by your application.
*
* @param token The new token.
*/
private fun sendRegistrationToServer(token: String?) {
// TODO: Implement this method to send token to your app server.
Log.d(TAG, "sendRegistrationTokenToServer($token)")
}
companion object {
private const val TAG = "PushNotificationHmsMessageService"
}
}
Testing on Postman
Notification Message
Step 1: Download below json file.
JSON:
{
"info": {
"_postman_id": "6ee69330-4edb-4b0c-b235-4247daa3e2ea",
"name": "PushKitTest",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Send notification",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "Bearer CgB6e3x9aAsJsxGgxgasZWXyu63iI9H/bwuVZFKbJfRjcCjP0LCJYpep4UAkZj33jknaHBRLWTRnTxHKcMfzOxBj",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"validate_only\": false,\n \"message\": \n {\n \"notification\":\n {\n \"title\": \"Huawei Push Kit\",\n \"body\": \"Hello, I'm a Push Kit.\"\n },\n \"android\":\n {\n \"notification\":\n {\n \"click_action\":\n {\n \"type\": 3\n }\n }\n },\n \"token\": [\"ANPpqLe8aTPl9tsB2Iu_820GmUQ-prH-dzPV-orbkm9yym2bitxd3emgxglrkVJW8hbTkusjHCu2_RW-AdnqqSbHevBQYwhjqOBzCdcu_Zrqfzuowi7Sxp4c2r_ztTORVQ\"]\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://push-api.cloud.huawei.com/v1/103346107/messages:send",
"protocol": "https",
"host": [
"push-api",
"cloud",
"huawei",
"com"
],
"path": [
"v1",
"103346107",
"messages:send"
]
}
},
"response": []
},
{
"name": "Send data message",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "Bearer CgB6e3x9aAsJsxGgxgasZWXyu63iI9H/bwuVZFKbJfRjcCjP0LCJYpep4UAkZj33jknaHBRLWTRnTxHKcMfzOxBj",
"type": "string"
}
]
},
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"validate_only\": false,\n \"message\": \n {\n \"data\": \"{\\\"title\\\":\\\"Huawei Push Kit\\\",\\\"text\\\":\\\"Hi, I'm a Push Kit.\\\",\\\"channel_id\\\":\\\"channel_1\\\"}\",\n \"token\": [\"ANPpqLe8aTPl9tsB2Iu_820GmUQ-prH-dzPV-orbkm9yym2bitxd3emgxglrkVJW8hbTkusjHCu2_RW-AdnqqSbHevBQYwhjqOBzCdcu_Zrqfzuowi7Sxp4c2r_ztTORVQ\"]\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "https://push-api.cloud.huawei.com/v1/103346107/messages:send",
"protocol": "https",
"host": [
"push-api",
"cloud",
"huawei",
"com"
],
"path": [
"v1",
"103346107",
"messages:send"
]
}
},
"response": []
},
{
"name": "Get access token",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/x-www-form-urlencoded"
}
],
"body": {
"mode": "urlencoded",
"urlencoded": [
{
"key": "grant_type",
"value": "client_credentials",
"type": "text"
},
{
"key": "client_id",
"value": "103346107",
"type": "text"
},
{
"key": "client_secret",
"value": "1e35055832e028a9b07251ad1f9192b2ffbf4551cd71bfbb005875b046f0b981",
"type": "text"
}
]
},
"url": {
"raw": "https://oauth-login.cloud.huawei.com/oauth2/v2/token",
"protocol": "https",
"host": [
"oauth-login",
"cloud",
"huawei",
"com"
],
"path": [
"oauth2",
"v2",
"token"
]
}
},
"response": []
}
]
}
Step 2: Open Postman. Import the file. (Postman > file > import) After you import, we can send 3 requests.
Step 3: Replace the app_id and app_secret values in the body with the App ID and App secret values on the application home page of the Huawei Console. Replace the fields in the image below with the values in your own project.
Step 4: Get push token in MainActivity.kt
Java:
private fun getToken() {
object : Thread() {
override fun run() {
try {
val appId = AGConnectOptionsBuilder().build([email protected])
.getString("client/app_id")
val token = HmsInstanceId.getInstance([email protected])
.getToken(appId, "HCM")
Log.i("PUSH", "getToken() token: $token")
} catch (e: ApiException) {
Log.e("PUSH", "getToken() failure: ${e.message}")
}
}
}.start()
}
Step 5: Open the Get access token file and click the Send button to get access token. The access token is valid for 3600 seconds and you need to renew it late.
Step 6: Open the Send notification and Send data message file and replace the access_token part in Authorization with the access token we got in before step. Don't forget to delete this characters \ in the access token.
After authorization token replaces, let us send the notification now.
Send Notification
Body:
JSON:
{
"validate_only": false,
"message":
{
"notification":
{
"title": "Push Ki Demo ",
"body": "Enter body here."
},
"android":
{
"notification":
{
"click_action":
{
"type": 3
}
}
},
"token": ["Add your push token here"]
}
}
Data Message
Body:
JSON:
{
"validate_only": false,
"message":
{
"data": "{\"title\":\"Huawei Push Kit\",\"text\":\"Hi, I'm a Push Kit.\",\"channel_id\":\"channel_1\"}",
"token": ["Add your push token here",
"Add your push token here"]
}
}
Result​
​Tips and Tricks​
1. Make sure you are already registered as a Huawei developer.
2. Set min SDK version to 19 or later, otherwise you will get AndriodManifest to merge issue.
3. Make sure you have added the agconnect-services.json file to the app folder.
4. Make sure you have added the SHA-256 fingerprint without fail.
5. Make sure all the dependencies are added properly.
Conclusion​
In this article, we have learnt the integration of the Huawei Push Kit in Paralysis Gloves mobile application using Android Studio and Kotlin. And also understood about Huawei Push kit, types of messages, notification type, How to test push notification from AppGallery console.
Reference​
Push Kit - Official document
Push Kit - Code lab
Push Kit - Training Video

Categories

Resources