[Huawei Developers] Sherlock F:
In this article I will explain how to develop a basic messaging app by Huawei Push Kit. If you want to develop a messaging app or add messaging section into your application, I hope this article will be useful for you.

Before beginning, this application and article will be addition of my first ones. If you have not seen that article, you can see the link below. It also shows how to integrate Push Kit into your Xamarin.Android Application.

https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0201314061860830181&fid=0101187876626530001

Layout of activity_main

We will only add our messaging button to pass to messaging section. Final version of “activity_main.xml” will be like this.

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100"
android:id="@+id/linearLayoutMain">
<Button
android:text="Get Token"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:id="@+id/btnGetToken" />
<Button
android:text="Send Test Notification"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:visibility="invisible"
android:id="@+id/btnNotification" />
<Button
android:text="Send Test Data Message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:visibility="invisible"
android:id="@+id/btnDataMessage" />
<Button
android:text="Messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="25px"
android:layout_weight="15"
android:visibility="invisible"
android:id="@+id/btnMessagingMain" />
</LinearLayout>

MainActivity Class

Firstly, we will inherit ViewAnimator.IOnClickListener interface to this class. Then, we will organize inside of OnCreate Method and implement OnClick method.

protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
SetContentView(Resource.Layout.activity_main);
//Identifying Buttons
btnGetToken = FindViewById<Button>(Resource.Id.btnGetToken);
btnNotification = FindViewById<Button>(Resource.Id.btnNotification);
btnDataMessage = FindViewById<Button>(Resource.Id.btnDataMessage);
btnMessagingMain = FindViewById<Button>(Resource.Id.btnMessagingMain);
//Set onClick listener to buttons
btnGetToken.SetOnClickListener(this);
btnNotification.SetOnClickListener(this);
btnDataMessage.SetOnClickListener(this);
btnMessagingMain.SetOnClickListener(this);
//Create instance of Broadcast Receiver for data message service
myReceiver = new MyBroadcastReceiver();
CheckPermission(new string[] { Android.Manifest.Permission.Internet,
Android.Manifest.Permission.AccessNetworkState,
Android.Manifest.Permission.WakeLock}, 100);
}

Secondly, we will arrange OnClick method so that we can call these events from one method.

public void OnClick(View v)
{
try
{
switch (v.Id)
{
case Resource.Id.btnGetToken:
GetToken();
break;
case Resource.Id.btnNotification:
HmsPushKit.SendNotification(tokenFromGetToken, "Title", "Body");
break;
case Resource.Id.btnDataMessage:
HmsPushKit.SendDataMessage(tokenFromGetToken, "{\"Type\":\"Test\",\"Message\":\"Welcome\"}");
break;
case Resource.Id.btnMessagingMain:
StartMessagingMain();
break;
default:
break;
}
}
catch (Exception e)
{
Log.Error("OnClickListener", e.ToString());
}
}

Thirdly, as you guess we will add StartMessagingMain method to call MessagingMainActivity class. Calling StartActivity method is adequate here. We don’t have to send any Intent.

Finally, we will move AccessToken, SendDataMessage and SendNotification method to new HmsPushKit class.

HmsPushKit.cs

We should be able to call these methods from any other class. Because of that, methods in this class should be static.

public static class HmsPushKit
{
static readonly string appID = "AppID"; //AppGallery Connect > Project Setting > App information > App ID
static readonly string appKey = "AppKey"; //AppGallery Connect > Project Setting > App information > App key
static readonly HttpClient client = new HttpClient();
public static async Task<string> GetAccessToken()
{
string uri = "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
var values = new Dictionary<string, string>
{
{ "grant_type", "client_credentials" },
{ "client_id", appID },
{ "client_secret", appKey }
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync(uri, content);
var jsonResponse = JObject.Parse(await response.Content.ReadAsStringAsync()); //Install-Package Newtonsoft.Json
string accessToken = jsonResponse["access_token"].ToString(); //It is valid for 3600 seconds
return accessToken;
}
/// <summary>
/// Send notification by Huawei Push Kit.
/// </summary>
/// <param name="tokenUSendTo">This token get by GetToken function</param>
/// <param name="notTitle">Notification Title</param>
/// <param name="notBody">Notification Body</param>
public static async void SendNotification(string tokenUSendTo,string notTitle,string notBody)
{
string uriNot = "https://push-api.cloud.huawei.com/v1/" + appID + "/messages:send";
var jObject = new
{
message = new
{
notification = new
{
title = notTitle,
body = notBody
},
android = new
{
notification = new
{
click_action = new
{
type = 3
}
}
},
token = new[] { tokenUSendTo }
}
};
string myJson = JsonConvert.SerializeObject(jObject, Formatting.Indented);
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", await GetAccessToken());
var responseData = await client.PostAsync(uriNot, new StringContent(myJson, Encoding.UTF8, "application/json"));
}
/// <summary>
/// Send data message by Huawei Push Kit.
/// </summary>
/// <param name="tokenUSendTo">This token get by GetToken function</param>
/// <param name="serializedObject">Use JsonConvert.SerializeObject function</param>
public static async void SendDataMessage(string tokenUSendTo, string serializedObject)
{
string uriNot = "https://push-api.cloud.huawei.com/v1/" + appID + "/messages:send";
var jObject = new
{
message = new
{
data = serializedObject,
token = new[] { tokenUSendTo }
}
};
string myJson = JsonConvert.SerializeObject(jObject, Formatting.Indented);
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", await GetAccessToken());
var responseData = await client.PostAsync(uriNot, new StringContent(myJson, Encoding.UTF8, "application/json"));
}
}

DataBase

Actually, database is not essential for a sample application but I want to make it pretty. I will use sqlite-net-pcl by SQLite-net.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/361/098/5190090000026361098.20200819083254.80833490569073856564678012458001:50511019062815:2800:53C685F8E253EC93B859929E53EEC06244DE363617C5AA1DD075B04BB907FE16.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/361/098/5190090000026361098.20200819083254.80833490569073856564678012458001:50511019062815:2800:53C685F8E253EC93B859929E53EEC06244DE363617C5AA1DD075B04BB907FE16.png[/IMG]

This application will need two essential database tables(Model) Person and Message. We will also use UniqueData table for minor data.

public class Person
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public string Token { get; set; }
public Person(string Code,string Name)
{
this.Code = Code;
this.Name = Name;
Random rnd = new Random();
Icon = "icon" + rnd.Next(1, 4);
}
public Person()
{
}
}
public class Message
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string CodeFrom { get; set; }
public string CodeTo { get; set; }
public string Text { get; set; }
}
public class UniqueData
{
public string Key { get; set; }
public string Value { get; set; }
}

And the Database class that I use is below. The most part is easy to understand. If you want to use another Model you should add this into CreateDataBase method. Moreover, you don’t have to check if there is a database. If database does not exist, it will create otherwise it will not.

public class DataBase
{
readonly string dbPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "DB.db");
readonly string _SQLite = "SQLite";
public DataBase()
{
CreateDataBase();
}
public bool CreateDataBase()
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
connection.CreateTable<Person>();
connection.CreateTable<Message>();
connection.CreateTable<UniqueDatas>();
return true;
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return false;
}
}
public bool InsertIntoTable<T>(T row)
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
connection.Insert(row);
return true;
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return false;
}
}
public List<T> SelectTable<T>() where T:new()
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
return connection.Table<T>().ToList();
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return null;
}
}
public bool DeleteFromTable<T>(T row)
{
try
{
using (var connection = new SQLiteConnection(dbPath))
{
connection.Delete(row);
return true;
}
}
catch (SQLiteException e)
{
Log.Error(_SQLite, e.Message);
return false;
}
}
}

Layout of activity_main_messaging

I will share with you a simple screen for the list of people.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/361/098/5190090000026361098.20200819083510.07825170547251736663821881868684:50511019062815:2800:9C4064237594AA45DA73764E8344E1D988D934B14AF35650DB943C7CB492A18E.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/361/098/5190090000026361098.20200819083510.07825170547251736663821881868684:50511019062815:2800:9C4064237594AA45DA73764E8344E1D988D934B14AF35650DB943C7CB492A18E.png[/IMG]

My xml file will not contain the rows. We will add them from MessagingMainActivity class. Xml code for this layout below.

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@color/material_grey_300"
android:weightSum="4"
android:paddingHorizontal="2dp">
<Button
android:text="Del"
android:gravity="center_horizontal"
android:textSize="24dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/btnDel"/>
<TextView
android:text="Chats"
android:gravity="center"
android:textStyle="bold"
android:textColor="@android:color/black"
android:textSize="24dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="4"
android:id="@+id/textView1"
/>
<Button
android:gravity="center_horizontal"
android:text="New"
android:textSize="24dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/btnNew"/>
</LinearLayout>
<ScrollView
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:scrollbars="vertical"
android:paddingHorizontal="20dp"
android:id="@+id/scrollView">
<LinearLayout
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/linearLayout"
android:divider="#B6B6B6"
android:dividerHeight="5px">
</LinearLayout>
</ScrollView>
</LinearLayout>

MessagingMainActivity Class

Things we will do in this class are bringing people from database, adding new person, deleting person and starting messaging. So let’s start with identifing Delete Button, New button and LinearLayout which is in the ScrollView.

After, we will set ClickListener on two buttons therefore, we will inherit ViewAnimator.IOnClickListener to this activity as well.

Later, we need to take instance of our DataBase class then add people into our LinearLayout. Furthermore, I will also add sample data.

At last, we will fill OnClick method.

protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main_messaging);
linearLayout = FindViewById<LinearLayout>(Resource.Id.linearLayout);
btnNew = FindViewById<Button>(Resource.Id.btnNew);
btnDel = FindViewById<Button>(Resource.Id.btnDel);
btnNew.SetOnClickListener(this);
btnDel.SetOnClickListener(this);
db = new DataBase();
if (db.SelectTable<Person>().Count == 0)
{
db.InsertIntoTable(new Person { Code = "0", Name = "Enes Durmus", Icon = "icon1" });
db.InsertIntoTable(new Person { Code = "1", Name = "Ahmet Ercek", Icon = "icon2" });
db.InsertIntoTable(new Person { Code = "2", Name = "Gokberk Bardakci", Icon = "icon3" });
}
foreach (Person person in db.SelectTable<Person>())
{
linearLayout.AddView(OneRowLL(this, person));
Log.Info("SQLite", person.ToString());
linearLayout.AddView(GetLine(this, Resources.DisplayMetrics.WidthPixels / 3));// This is just decorative
}
}
public void OnClick(View v)
{
try
{
switch (v.Id)
{
case Resource.Id.btnNew:
AddPerson();
break;
case Resource.Id.btnDel:
IsDelete = true;
break;
default:
break;
}
}
catch (Exception e)
{
Log.Error("OnClickListener", e.ToString());
}
}

As you see there is OneRowLL method. It returns a linearlayout which contains CircleImageView from Refractored.Controls.CircleImage nuget and linearlayout contains two textviews.

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/361/098/5190090000026361098.20200819083608.93812310075949921635207703298071:50511019062815:2800:5AD8EC1CDDEB4DE60FFD5FD828026619B5148258FDDD1A9F59C0CF06072C4F5B.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/361/098/5190090000026361098.20200819083608.93812310075949921635207703298071:50511019062815:2800:5AD8EC1CDDEB4DE60FFD5FD828026619B5148258FDDD1A9F59C0CF06072C4F5B.png[/IMG]

LinearLayout OneRowLL(Context context, Person person)
{
LinearLayout result = new LinearLayout(context);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MatchParent, LinearLayout.LayoutParams.WrapContent);
result.LayoutParameters = layoutParams;
result.Orientation = Orientation.Horizontal;
result.TransitionName = person.Code;
result.WeightSum = 1;
ViewGroup.LayoutParams vgLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.MatchParent);
CircleImageView imageView = new CircleImageView(context); // Nuget: Refractored.Controls.CircleImageView
LinearLayout.LayoutParams iLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WrapContent, LinearLayout.LayoutParams.MatchParent) { Weight = 0.6f };
imageView.LayoutParameters = iLayoutParams;
int id = Resources.GetIdentifier("com.companyname.pushdemoforarticle:drawable/" + person.Icon, null, null);
imageView.SetImageResource(id);
imageView.BorderWidth = 6;
imageView.BorderColor = -16777216;
result.AddView(imageView);
LinearLayout subLinearLayout = new LinearLayout(context);
layoutParams.Weight = 1;
subLinearLayout.LayoutParameters = layoutParams;
subLinearLayout.Orientation = Orientation.Vertical;
subLinearLayout.SetPadding(20, 0, 0, 0);
ViewGroup.LayoutParams txtViewLayoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
subLinearLayout.AddView(new TextView(context)
{
Text = person.Name,
TextSize = 24,
LayoutParameters = txtViewLayoutParams,
Gravity = GravityFlags.Start
});
string _lastMessage = string.Empty;
List<Message> messages = db.SelectTable<Message>().Where(x => x.CodeFrom == person.Code || x.CodeTo == person.Code).ToList();
if (messages.Count != 0)
{
Message lastMessage = messages.TakeLast(1).First();
_lastMessage = lastMessage.CodeFrom == self ? "You: "+lastMessage.Text : lastMessage.Text;
}
subLinearLayout.AddView(new TextView(context)
{
Text = _lastMessage,
LayoutParameters = txtViewLayoutParams,
});
result.AddView(subLinearLayout);
result.Click += LinearLayoutClick;
return result;
}

Afterwards, we will create AddPerson method for btnNew. We will use LayoutInflater so I also create a xml file as well. We need user’s code to get his token and a name to display it.

private void AddPerson()
{
AlertDialog dialog = InitDialog();
dialog.Show();
}
public AlertDialog InitDialog()
{
//Setting xml from layouts
LayoutInflater layoutInflater = LayoutInflater.From(this);
View view = layoutInflater.Inflate(Resource.Layout.input_box, null);
AlertDialog.Builder alertbuilder = new AlertDialog.Builder(this);
alertbuilder.SetView(view);
//Identifing EditTexts
var userCode = view.FindViewById<EditText>(Resource.Id.txtUserCode);
var userName = view.FindViewById<EditText>(Resource.Id.txtUserName);
//Setting Positive and Negative Buttons
alertbuilder.SetCancelable(false)
.SetPositiveButton("OK", delegate
{
Person person = new Person(userCode.Text.Trim(), userName.Text);
if (db.InsertIntoTable(person))
linearLayout.AddView(OneRowLL(this, person));
}).SetNegativeButton("Cancel", delegate { });
AlertDialog dialog = alertbuilder.Create();
return dialog;
}

<?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">
<TextView
android:id="@+id/dialogTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Your Friend"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/txtUserCode"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Your Friend's Code" />
<EditText
android:id="@+id/txtUserName"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Your Friend's Name" />
</LinearLayout>

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/361/098/5190090000026361098.20200819083703.18527436280622454026849491267726:50511019062815:2800:FC06EA5B603407A2EE3A8586F2BC42DE9D2ECBBBC0211103ECDE1FC4CBA8AAA8.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/361/098/5190090000026361098.20200819083703.18527436280622454026849491267726:50511019062815:2800:FC06EA5B603407A2EE3A8586F2BC42DE9D2ECBBBC0211103ECDE1FC4CBA8AAA8.png[/IMG]

Next step is btnDel. Delete button will be simple. Click delete then click person afterwards person is gone. That’s adequate for now but you can improve it by adding invisible red delete button to each row and ask for approve.

Finally, LinearLayoutClick for pass to messaging. We will send their code by Intent.

public void LinearLayoutClick(object sender, EventArgs e)
{
LinearLayout row = sender as LinearLayout;
if (!IsDelete)
{
Intent intent = new Intent(this, typeof(MessagingActivity));
intent.PutExtra("code", row.TransitionName);
StartActivity(intent);
}
else
{
linearLayout.RemoveView(row);
IsDelete = false;
}
}

Layout of activity_messaging.xml

There will be 3 sections. First one is the LinearLayout which shows who we message. Second one is ScrollView for messages. And the last one is EditText for typing and sending. Xml file below.

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100"
android:id="@+id/linearLayoutMain">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="100"
android:id="@+id/ll0"
android:transitionName="0"
android:paddingLeft="@dimen/abc_select_dialog_padding_start_material">
<refractored.controls.CircleImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="60"
app:civ_border_width="2dp"
app:civ_border_color="#000000"
android:id="@+id/imageIcon"/>
<LinearLayout
android:paddingLeft="10dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="100">
<TextView
android:textSize="24dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:id="@+id/txtName"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtStatus"/>
</LinearLayout>
</LinearLayout>
<ScrollView
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="100"
android:gravity="bottom"
android:animateLayoutChanges="true"
android:orientation="vertical"
android:scrollbars="vertical"
android:paddingHorizontal="20dp"
android:id="@+id/scrollView">
<LinearLayout
android:orientation="vertical"
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linearLayout" />
</ScrollView>
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="20"
android:id="@+id/textInput"
android:hint="Type here!"
android:layout_marginBottom="4dp"/>
</LinearLayout>

[IMG="https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/361/098/5190090000026361098.20200819083936.93018965423070171936271071762975:50511019062815:2800:BCF808366E217DF076B251EC2A94DD66B6956104B6E1247D9DD5348DE15D41B8.png"]https://communityfile-dre.op.hicloud.com/FileServer/getFile/cmtybbs/026/361/098/5190090000026361098.20200819083936.93018965423070171936271071762975:50511019062815:2800:BCF808366E217DF076B251EC2A94DD66B6956104B6E1247D9DD5348DE15D41B8.png[/IMG]

For more information, you can visit https://forums.developer.huawei.com/forumPortal/en/topicview?tid=0202327073597950015&fid=0101187876626530001