[Huawei Developers] Freemind R:
More information like this, you can visit HUAWEI Developer Forum​

A geofence is a virtual perimeter set on a real geographic area. Combining a user position with a geofence perimeter, it is possible to know if the user is inside the geofence or if he is exiting or entering the area.

In this article, we will discuss how to use the geofence to notify the user when the device enters/exits an area using the HMS Location Kit in a Xamarin.Android application. We will also add and customize HuaweiMap, which includes drawing circles, adding pointers, and using nearby searches in search places. We are going to learn how to use the below features together:

Geofence

Reverse Geocode

HuaweiMap

Nearby Search

Project Setup

First of all, you need to be a registered Huawei Mobile Developer and create an application in Huawei App Console in order to use HMS Map Location and Site Kits. You can follow these steps to complete the configuration that required for development:

Configuring App Information in AppGallery Connect

Creating Xamarin Android Binding Libraries

Integrating the HMS Map Kit Libraries for Xamarin

Integrating the HMS Location Kit Libraries for Xamarin

Integrating the HMS Site Kit Libraries for Xamarin

Integrating the HMS Core SDK

Setting Package in Xamarin

When we create our Xamarin.Android application in the above steps, we need to make sure that the package name is the same as we entered the Console. Also, don’t forget the enable them in Console.

Manifest & Permissions

We have to update the application’s manifest file by declaring permissions that we need as shown below.

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

Also, add a meta-data element to embed your app id in the application tag, it is required for this app to authenticate on the Huawei’s cloud server. You can find this id in agconnect-services.json file.

<meta-data android:name="com.huawei.hms.client.appid" android:value="appid=YOUR_APP_ID" />

Request location permission

Request runtime permissions in our app in order to use Location and Map Services. The following code checks whether the user has granted the required location permissions in Main Activity.

private void RequestPermissions()
{
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.AccessCoarseLocation) != (int)Permission.Granted ||
ContextCompat.CheckSelfPermission(this, Manifest.Permission.AccessFineLocation) != (int)Permission.Granted ||
ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != (int)Permission.Granted ||
ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) != (int)Permission.Granted ||
ContextCompat.CheckSelfPermission(this, Manifest.Permission.Internet) != (int)Permission.Granted)
{
ActivityCompat.RequestPermissions(this,
new System.String[]
{
Manifest.Permission.AccessCoarseLocation,
Manifest.Permission.AccessFineLocation,
Manifest.Permission.WriteExternalStorage,
Manifest.Permission.ReadExternalStorage,
Manifest.Permission.Internet
},
100);
}
else
GetCurrentPosition();
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
if (requestCode == 100)
{
foreach (var item in permissions)
{
if (ContextCompat.CheckSelfPermission(this, item) == Permission.Denied)
{
if (ActivityCompat.ShouldShowRequestPermissionRationale(this, permissions[0]) || ActivityCompat.ShouldShowRequestPermissionRationale(this, permissions[1]))
Snackbar.Make(FindViewById<RelativeLayout>(Resource.Id.mainLayout), "You need to grant permission to use location services.", Snackbar.LengthLong).SetAction("Ask again", v => RequestPermissions()).Show();
else
Toast.MakeText(this, "You need to grant location permissions in settings.", ToastLength.Long).Show();
}
else
GetCurrentPosition();
}
}
else
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

Add a Map

Within our UI, a map will be represented by either a MapFragment or MapView object. We will use the MapFragment object in this sample.

Add a <fragment> element to your activity’s layout file, activity_main.xml. This element defines a MapFragment to act as a container for the map and to provide access to the HuaweiMap object.

Also, let’s add other controls to use through this sample. That is two Button and a SeekBar. One button for clearing the map and the other for searching nearby locations. And seekbar is helping us to create a radius for the geofence.

<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:map="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mainLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/mapfragment"
class="com.huawei.hms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<Button
android:text="Get Geofence List"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:padding="5dp"
android:background="@drawable/abc_btn_colored_material"
android:textColor="@android:color/white"
android:id="@+id/btnGetGeofenceList" />
<Button
android:text="Clear Map"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="@drawable/abc_btn_colored_material"
android:textColor="@android:color/white"
android:id="@+id/btnClearMap" />
</LinearLayout>
<SeekBar
android:visibility="invisible"
android:min="30"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/radiusBar" />
</RelativeLayout>

In our activity’s OnCreate method, set the layout file as the content view, load AGConnectService, set button’s click events, and initialize FusedLocationProviderClient. Get a handle to the map fragment by calling FragmentManager.FindFragmentById. Then use GetMapAsync to register for the map callback.

Also, implement the IOnMapReadyCallback interface to MainActivity and override OnMapReady method which is triggered when the map is ready to use.

public class MainActivity : AppCompatActivity, IOnMapReadyCallback
{
MapFragment mapFragment;
HuaweiMap hMap;
Marker marker;
Circle circle;
SeekBar radiusBar;
FusedLocationProviderClient fusedLocationProviderClient;
GeofenceModel selectedCoordinates;
List<Marker> searchMarkers;
private View search_view;
private AlertDialog alert;
public static LatLng CurrentPosition { get; set; }
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
SetContentView(Resource.Layout.activity_main);
AGConnectServicesConfig config = AGConnectServicesConfig.FromContext(ApplicationContext);
fusedLocationProviderClient = LocationServices.GetFusedLocationProviderClient(this);
mapFragment = (MapFragment)FragmentManager.FindFragmentById(Resource.Id.mapfragment);
mapFragment.GetMapAsync(this);
FindViewById<Button>(Resource.Id.btnGeoWithAddress).Click += btnGeoWithAddress_Click;
FindViewById<Button>(Resource.Id.btnClearMap).Click += btnClearMap_Click;
radiusBar = FindViewById<SeekBar>(Resource.Id.radiusBar);
radiusBar.ProgressChanged += radiusBar_ProgressChanged; ;
RequestPermissions();
}
public void OnMapReady(HuaweiMap map)
{
hMap = map;
hMap.UiSettings.MyLocationButtonEnabled = true;
hMap.UiSettings.CompassEnabled = true;
hMap.UiSettings.ZoomControlsEnabled = true;
hMap.UiSettings.ZoomGesturesEnabled = true;
hMap.MyLocationEnabled = true;
hMap.MapClick += HMap_MapClick;
if (selectedCoordinates == null)
selectedCoordinates = new GeofenceModel { LatLng = CurrentPosition, Radius = 30 };
}
}

As you can see above, with the UiSettings property of the HuaweiMap object we set my location button, enable compass, etc. Other properties like below:

public bool CompassEnabled { get; set; }
public bool IndoorLevelPickerEnabled { get; set; }
public bool MapToolbarEnabled { get; set; }
public bool MyLocationButtonEnabled { get; set; }
public bool RotateGesturesEnabled { get; set; }
public bool ScrollGesturesEnabled { get; set; }
public bool ScrollGesturesEnabledDuringRotateOrZoom { get; set; }
public bool TiltGesturesEnabled { get; set; }
public bool ZoomControlsEnabled { get; set; }
public bool ZoomGesturesEnabled { get; set; }

Now when the app launch, directly get the current location and move the camera to it. In order to do that we use FusedLocationProviderClient that we instantiated and call LastLocation API.

LastLocation API returns a Task object that we can check the result by implementing the relevant listeners for success and failure.In success listener we are going to move the map’s camera position to the last known position.

private void GetCurrentPosition()
{
var locationTask = fusedLocationProviderClient.LastLocation;
locationTask.AddOnSuccessListener(new LastLocationSuccess(this));
locationTask.AddOnFailureListener(new LastLocationFail(this));
}
...
public class LastLocationSuccess : Java.Lang.Object, IOnSuccessListener
{
private MainActivity mainActivity;
public LastLocationSuccess(MainActivity mainActivity)
{
this.mainActivity = mainActivity;
}
public void OnSuccess(Java.Lang.Object location)
{
Toast.MakeText(mainActivity, "LastLocation request successful", ToastLength.Long).Show();
if (location != null)
{
MainActivity.CurrentPosition = new LatLng((location as Location).Latitude, (location as Location).Longitude);
mainActivity.RepositionMapCamera((location as Location).Latitude, (location as Location).Longitude);
}
}
}

To change the position of the camera, we must specify where we want to move the camera, using a CameraUpdate. The Map Kit allows us to create many different types of CameraUpdate using CameraUpdateFactory.

public static CameraUpdate NewCameraPosition(CameraPosition p0);
public static CameraUpdate NewLatLng(LatLng p0);
public static CameraUpdate NewLatLngBounds(LatLngBounds p0, int p1);
public static CameraUpdate NewLatLngBounds(LatLngBounds p0, int p1, int p2, int p3);
public static CameraUpdate NewLatLngZoom(LatLng p0, float p1);
public static CameraUpdate ScrollBy(float p0, float p1);
public static CameraUpdate ZoomBy(float p0);
public static CameraUpdate ZoomBy(float p0, Point p1);
public static CameraUpdate ZoomIn();
public static CameraUpdate ZoomOut();
public static CameraUpdate ZoomTo(float p0);

There are some methods for the camera position changes as we see above. Simply these are:

1. NewLatLng: Change camera’s latitude and longitude, while keeping other properties

2. NewLatLngZoom: Changes the camera’s latitude, longitude, and zoom, while keeping other properties

3. NewCameraPosition: Full flexibility in changing the camera position

We are going to use NewCameraPosition. A CameraPosition can be obtained with a CameraPosition.Builder. And then we can set target, bearing, tilt and zoom properties.

public void RepositionMapCamera(double lat, double lng)
{
var cameraPosition = new CameraPosition.Builder();
cameraPosition.Target(new LatLng(lat, lng));
cameraPosition.Zoom(1000);
cameraPosition.Bearing(45);
cameraPosition.Tilt(20);
CameraUpdate cameraUpdate = CameraUpdateFactory.NewCameraPosition(cameraPosition.Build());
hMap.MoveCamera(cameraUpdate);
}

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/310/157/5190090000026310157.20200917073037.50159749217943596551789204064008:50510928022413:2800:6385667B32C3E0747B60F83D32227B75F265C5FAC74B7B31F179C53452B54841.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/310/157/5190090000026310157.20200917073037.50159749217943596551789204064008:50510928022413:2800:6385667B32C3E0747B60F83D32227B75F265C5FAC74B7B31F179C53452B54841.png[/IMG]

Creating Geofence

Now that we’ve created the map, we can now start to create geofences using it. In this article, we will choose the location where we want to set geofence in two different ways. The first is to select the location by clicking on the map, and the second is to search for nearby places by keyword and select one after placing them on the map with the marker.

Set the geofence location by clicking on the map

It is always easier to select a location by seeing it. After this section, we are able to set a geofence around the clicked point when the map’s clicked. We attached the Click event to our map in the OnMapReady method. In this Click event, we will add a marker to the clicked point and draw a circle around it.

After clicking the map, we will add a circle, a marker, and a custom info window to that point like this:

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/310/157/5190090000026310157.20200917073107.22511944784505936062579444994764:50510928022413:2800:2531711793ECD472C43CEE593A5CFD7E3D10166E84C8D939395E1C46587A4F28.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/310/157/5190090000026310157.20200917073107.22511944784505936062579444994764:50510928022413:2800:2531711793ECD472C43CEE593A5CFD7E3D10166E84C8D939395E1C46587A4F28.png[/IMG]

Also, we will use the Seekbar at the bottom of the page to adjust the circle radius.

We set selectedCoordinates variable when adding the marker. Let’s create the following method to create the marker:

private void HMap_MapClick(object sender, HuaweiMap.MapClickEventArgs e)
{
selectedCoordinates.LatLng = e.P0;
if (circle != null)
{
circle.Remove();
circle = null;
}
AddMarkerOnMap();
}
void AddMarkerOnMap()
{
if (marker != null) marker.Remove();
var markerOption = new MarkerOptions()
.InvokeTitle("You are here now")
.InvokePosition(selectedCoordinates.LatLng);
hMap.SetInfoWindowAdapter(new MapInfoWindowAdapter(this));
marker = hMap.AddMarker(markerOption);
bool isInfoWindowShown = marker.IsInfoWindowShown;
if (isInfoWindowShown)
marker.HideInfoWindow();
else
marker.ShowInfoWindow();
}

With MarkerOptions we can set the title and position properties. And for creating a custom info window, there is SetInfoWindowAdapter method. Adding MapInfoWindowAdapter class to our project for rendering the custom info model. And implement HuaweiMap.IInfoWindowAdapter interface to it.

This interface provides a custom information window view of a marker and contains two methods:

View GetInfoContents(Marker marker);
View GetInfoWindow(Marker marker);

When an information window needs to be displayed for a marker, methods provided by this adapter are called in any case.

Now let’s create a custom info window layout and named it as map_info_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="Add geofence"
android:width="100dp"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnInfoWindow" />
</LinearLayout>

And return it after customizing it in GetInfoWindow() method. The full code of the adapter is below:

internal class MapInfoWindowAdapter : Java.Lang.Object, HuaweiMap.IInfoWindowAdapter
{
private MainActivity activity;
private GeofenceModel selectedCoordinates;
private View addressLayout;
public MapInfoWindowAdapter(MainActivity currentActivity)
{
activity = currentActivity;
}
public View GetInfoContents(Marker marker)
{
return null;
}
public View GetInfoWindow(Marker marker)
{
if (marker == null)
return null;
//update everytime, drawcircle need it
selectedCoordinates = new GeofenceModel { LatLng = new LatLng(marker.Position.Latitude, marker.Position.Longitude) };
View mapInfoView = activity.LayoutInflater.Inflate(Resource.Layout.map_info_view, null);
var radiusBar = activity.FindViewById<SeekBar>(Resource.Id.radiusBar);
if (radiusBar.Visibility == Android.Views.ViewStates.Invisible)
{
radiusBar.Visibility = Android.Views.ViewStates.Visible;
radiusBar.SetProgress(30, true);
}
activity.FindViewById<SeekBar>(Resource.Id.radiusBar)?.SetProgress(30, true);
activity.DrawCircleOnMap(selectedCoordinates);
Button button = mapInfoView.FindViewById<Button>(Resource.Id.btnInfoWindow);
button.Click += btnInfoWindow_ClickAsync;
return mapInfoView;
}
}

This is not the end. For full content, you can visit https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201357111605920240&fid=0101187876626530001