[Huawei Developers] John Wo:
In this article, I am going to use 3 Huawei kits in one project:

· Map Kit, for personalizing how your map displays and interact with your users, also making location-based services work better for your users.

· Location Kit, for getting the user’s current location with fused location function, also creating geofences.

· Site Kit, for searching and exploring the nearby places with their addresses.

What is a Geo-fence?

Geofence literally means a virtual border around a geographic area. Geofencing technology is the name of the technology used to trigger an automatic alert when an active device enters a defined geographic area (geofence).

As technology developed, brands started to reach customers. Of course, at this point, with digital developments, multiple new marketing terms started to emerge. Geofencing, a new term that emerged with this development, entered the lives of marketers.

Project Setup

HMS Integration

Firstly, you need a Huawei Developer account and add an app in Projects in AppGallery Connect console. So that you can activate the Map, Location and Site kits and use them in your app. If you don’t have an Huawei Developer account and don’t know the steps please follow the links below.

· Register Huawei developer website

· Configuring app information in AppGallery Connect

· Integrating Map Kit Flutter Plugin

· Integrating Location Kit Flutter Plugin

· Integrating Site Kit Flutter Plugin

Important: While adding app, the package name you enter should be the same as your Flutter project’s package name.

Note: Before you install agconnect-services.json file, make sure the required kits are enabled.

Permissions

In order to make your kits work perfectly, you need to add the permissions below in AndroidManifest.xml file.

<uses-permission android:name="android.permission.INTERNET"/>

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

Creating Flutter Application

Add Dependencies to ‘pubspec.yaml’

After completing all the steps above, you need to add the required kits’ Flutter plugins as dependencies to pubspec.yaml file. You can find all the plugins in pub.dev with the latest versions. You can follow the steps in installing section of the following links.

· Map Kit Plugin for Flutter

· Location Kit Plugin for Flutter

· Site Kit Plugin for Flutter

dependencies:
flutter:
sdk: flutter
huawei_location: ^5.0.0+301
huawei_site: ^5.0.1+300
huawei_map: ^4.0.4+300

After adding them, run flutter pub get command.

All the plugins are ready to use!

Request Location Permission and Get Current Location

Create a PermissionHandler instance and initialize it in initState to ask for permission. Also, follow the same steps for FusedLocationProviderClient. With locationService object, we can get the user’s current location by calling getLastLocation() method.

LatLng center;
PermissionHandler permissionHandler;
FusedLocationProviderClient locationService;

@override
void initState() {
permissionHandler = PermissionHandler();
locationService = FusedLocationProviderClient();
getCurrentLatLng();
super.initState();
}

getCurrentLatLng() async {
await requestPermission();
Location currentLocation = await locationService.getLastLocation();
LatLng latLng = LatLng(currentLocation.latitude, currentLocation.longitude);
setState(() {
center = latLng;
});
}

In requestPermission() method, you can find both Location Permission and Background Location Permission.

requestPermission() async {
bool hasPermission = await permissionHandler.hasLocationPermission();
if (!hasPermission) {
try {
bool status = await permissionHandler.requestLocationPermission();
print("Is permission granted $status");
} catch (e) {
print(e.toString());
}
}
bool backgroundPermission =
await permissionHandler.hasBackgroundLocationPermission();
if (!backgroundPermission) {
try {
bool backStatus =
await permissionHandler.requestBackgroundLocationPermission();
print("Is background permission granted $backStatus");
} catch (e) {
print(e.toString);
}
}
}

When you launch the app for the first time, the location permission screen will appear.

Add HuaweiMap

Huawei Map is the main layout of this project. It will cover all the screen and also we will add some buttons on it, so we should put HuaweiMap and other widgets into a Stack widget. Do not forget to create a Huawei map controller.

static const double _zoom = 16;
Set<Marker> _markers = {};
int _markerId = 1;
Set<Circle> _circles = {};
int _circleId = 1;

_onMapCreated(HuaweiMapController controller) {
mapController = controller;
}

Stack(
fit: StackFit.expand,
children: <Widget>[
HuaweiMap(
onMapCreated: _onMapCreated,
initialCameraPosition:
CameraPosition(target: center, zoom: _zoom),
mapType: MapType.normal,
onClick: (LatLng latLng) {
placeSearch(latLng);
selectedCoordinates = latLng;
_getScreenCoordinates(latLng);
setState(() {
clicked = true;
addMarker(latLng);
addCircle(latLng);
});
},
markers: _markers,
circles: _circles,
tiltGesturesEnabled: true,
buildingsEnabled: true,
compassEnabled: true,
zoomControlsEnabled: true,
rotateGesturesEnabled: true,
myLocationButtonEnabled: true,
myLocationEnabled: true,
trafficEnabled: false,
),
],
)

We have got the current location with Location service’s getLastLocation() method and assigned it to center variables as longitude and latitude. While creating the HuaweiMap widget, assign that center variable to HuaweiMap’s target property, so that the app opens with a map showing the user’s current location.

placeSearch(LatLng latLng) async {
NearbySearchRequest request = NearbySearchRequest();
request.location = Coordinate(lat: latLng.lat, lng: latLng.lng);
request.language = "en";
request.poiType = LocationType.ADDRESS;
request.pageIndex = 1;
request.pageSize = 1;
request.radius = 100;
NearbySearchResponse response = await searchService.nearbySearch(request);
try {
print(response.sites);
site = response.sites[0];
} catch (e) {
print(e.toString());
}
}

When onClick method of HuaweiMap is triggered, call placeSearch using the Site Kit’s nearbySearch method. Thus, you will get a Site object to assign to the new geofence you will add.

Create Geofence

When the user touch somewhere on the map; a marker, a circle around the marker, a Slider widget to adjust the radius of the circle, and a button named “Add Geofence” will show up on the screen. So we will use a boolean variable called clicked and if it’s true, the widgets I have mentioned in the last sentence will be shown.

addMarker(LatLng latLng) {
if (marker != null) marker = null;
marker = Marker(
markerId: MarkerId(_markerId.toString()), //_markerId is set to 1
position: latLng,
clickable: true,
icon: BitmapDescriptor.defaultMarker,
);
setState(() {
_markers.add(marker);
});
selectedCoordinates = latLng;
_markerId++; //after a new marker is added, increase _markerId for the next marker
}

_drawCircle(Geofence geofence) {
this.geofence = geofence;
if (circle != null) circle = null;
circle = Circle(
circleId: CircleId(_circleId.toString()),
fillColor: Colors.grey[400],
strokeColor: Colors.red,
center: selectedCoordinates,
clickable: false,
radius: radius,
);
setState(() {
_circles.add(circle);
});
_circleId++;
}

Create a Slider widget wrapped with a Positioned widget and put them into Stack widget as shown below.

if (clicked)
Positioned(
bottom: 10,
right: 10,
left: 10,
child: Slider(
min: 50,
max: 200,
value: radius,
onChanged: (newValue) {
setState(() {
radius = newValue;
_drawCircle(geofence);
});
},
),
),

After implementing addMarker and drawCircle methods and adding Slider widget, now we will create AddGeofence Screen and it will appear as a ModalBottomSheet when AddGeofence button is clicked.

RaisedButton(
child: Text("Add Geofence"),
onPressed: () async {
geofence.uniqueId = _fenceId.toString();
geofence.radius = radius;
geofence.latitude = selectedCoordinates.lat;
geofence.longitude = selectedCoordinates.lng;
_fenceId++;
final clickValue = await showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => SingleChildScrollView(
child: Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: AddGeofenceScreen(
geofence: geofence,
site: site,
),
),
),
);
updateClicked(clickValue);
//When ModalBottomSheet is closed, pass a bool value in Navigator
//like Navigator.pop(context, false) so that clicked variable will be
//updated in home screen with updateClicked method.
},
),

void updateClicked(bool newValue) {
setState(() {
clicked = newValue;
});
}

In the new stateful AddGeofenceScreen widget’s state class, create GeofenceService and SearchService instances and initialize them in initState.

GeofenceService geofenceService;
int selectedConType = Geofence.GEOFENCE_NEVER_EXPIRE;
SearchService searchService;

@override
void initState() {
geofenceService = GeofenceService();
searchService = SearchService();
super.initState();
}

To monitor address, radius and also to select conversion type of the geofence, we will show a ModalBottomSheet with the widgets shown below.

Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
"Address",
style: boldStyle,
),
Text(site.formatAddress),
Text(
"\nRadius",
style: boldStyle,
),
Text(geofence.radius.toInt().toString()),
Text(
"\nSelect Conversion Type",
style: boldStyle,
),
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
RadioListTile<int>(
dense: true,
title: Text(
"Enter",
style: TextStyle(fontSize: 14),
),
value: Geofence.ENTER_GEOFENCE_CONVERSION,
groupValue: selectedConType,
onChanged: (int value) {
setState(() {
selectedConType = value;
});
},
),
RadioListTile<int>(
dense: true,
title: Text("Exit"),
value: Geofence.EXIT_GEOFENCE_CONVERSION,
groupValue: selectedConType,
onChanged: (int value) {
setState(() {
selectedConType = value;
});
},
),
RadioListTile<int>(
dense: true,
title: Text("Stay"),
value: Geofence.DWELL_GEOFENCE_CONVERSION,
groupValue: selectedConType,
onChanged: (int value) {
setState(() {
selectedConType = value;
});
},
),
RadioListTile<int>(
dense: true,
title: Text("Never Expire"),
value: Geofence.GEOFENCE_NEVER_EXPIRE,
groupValue: selectedConType,
onChanged: (int value) {
setState(() {
selectedConType = value;
});
},
),
],
),
Align(
alignment: Alignment.bottomRight,
child: FlatButton(
child: Text(
"SAVE",
style: TextStyle(
color: Colors.blue, fontWeight: FontWeight.bold),
),
onPressed: () {
geofence.conversions = selectedConType;
addGeofence(geofence);
Navigator.pop(context, false);
},
),
)
],
),

For each conversion type, add a RadioListTile widget.

When you click SAVE button, addGeofence method will be called to add new Geofence to the list of Geofences, then return to the Home screen with false value to update clicked variable.

In addGeofence, do not forget to call createGeofenceList method with the list you have just added the geofence in.

void addGeofence(Geofence geofence) {
geofence.dwellDelayTime = 10000;
geofence.notificationInterval = 100;
geofenceList.add(geofence);

GeofenceRequest geofenceRequest = GeofenceRequest(geofenceList:
geofenceList);
try {
int requestCode = await geofenceService.createGeofenceList
(geofenceRequest);
print(requestCode);
} catch (e) {
print(e.toString());
}
}

To listen to the geofence events, you need to use onGeofenceData method in your code.

GeofenceService geofenceService;
StreamSubscription<GeofenceData> geofenceStreamSub;

@override
void initState() {
geofenceService = GeofenceService();
geofenceStreamSub = geofenceService.onGeofenceData.listen((data) {
infoText = data.toString(); //you can use this infoText to show a toast message to the user.
print(data.toString);
});
super.initState();
}

Search Nearby Places

In home screen, place a button onto the map to search nearby places with a keyword and when it is clicked a new alertDialog page will show up.

void _showAlertDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Search Location"),
content: Container(
height: 150,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
TextField(
controller: searchQueryController,
),
MaterialButton(
color: Colors.blue,
child: Text(
"Search",
style: TextStyle(color: Colors.white),
),
onPressed: () async {
Navigator.pop(context);
_markers =
await nearbySearch(center, searchQueryController.text);
setState(() {});
},
)
],
),
),
actions: [
FlatButton(
child: Text("Close"),
onPressed: () {
Navigator.pop(context);
},
),
],
);
},
);
}

After you enter the keyword and click Search button, there will be markers related to the keyword will appear on the map.

Conclusion

In this article you have learnt how to use some of the features of Huawei Map, Location and Site kits in your projects. Also, you have learnt the geofencing concept. Now you can add geofences to your app and with geofencing, you can define an audience based on a customer’s behavior in a specific location. With location information, you can show suitable ads to the right people simultaneously, wherever they are.

Thank you for reading this article, I hope it was useful and you enjoyed it!