Acumatica mobile push notification without using Business Event - c#

How do I send mobile push notification without using Business Event ?

It is possible to send mobile push notification without using Business Events to open screen in mobile application when user taps the notification.
Below example describes approach :
using System;
using System.Collections;
using System.Linq;
using System.Threading;
using CommonServiceLocator;
using PX.Api.Mobile.PushNotifications;
using PX.Common;
using PX.Data;
using PX.Objects.SO;
namespace PX.PushNotificatioinSample.Ext
{
public class SOOrderEntryPXExt : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> ViewOnMobileApp;
[PXUIField(DisplayName = Messages.ViewActionDisplayName,
MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Default)]
public virtual IEnumerable viewOnMobileApp(PXAdapter adapter)
{
if (Base.Document.Cache.GetStatus(Base.Document.Current) == PXEntryStatus.Inserted ||
Base.Document.Cache.GetStatus(Base.Document.Current) == PXEntryStatus.InsertedDeleted) { return adapter.Get(); }
//Get instance of PushNotification service
var pushNotificationSender = ServiceLocator.Current.GetInstance<IPushNotificationSender>();
//Users to whom message will be sent
var userIds = new[] { PXAccess.GetUserID() };
//Check if User is using Acumatica Mobile App
var activeTokens = pushNotificationSender.CountActiveTokens(userIds);
if (activeTokens == 0)
{
throw new PXException(Messages.NoDeviceError);
}
string sOrderNbr = Base.Document.Current.OrderNbr;
string sScreenID = Base.Accessinfo.ScreenID.Replace(".", "");
Guid noteID = Base.Document.Current.NoteID.Value;
PXLongOperation.StartOperation(Base, () =>
{
try
{
pushNotificationSender.SendNotificationAsync(
userIds: userIds,
// Push Notification Title
title: Messages.PushNotificationTitle,
// Push Notification Message Body
text: $"{ Messages.PushNotificationMessageBody } { sOrderNbr }.",
// Link to Screen to open upon tap with Sales Order data associated to NoteID
link: (sScreenID, noteID),
cancellation: CancellationToken.None);
}
catch (AggregateException ex)
{
var message = string.Join(";", ex.InnerExceptions.Select(c => c.Message));
throw new InvalidOperationException(message);
}
});
return adapter.Get();
}
}
[PXLocalizable]
public static class Messages
{
public const string ViewActionDisplayName = "View On Mobile App";
public const string NoDeviceError = "You need to set up the Acumatica mobile app.";
public const string PushNotificationTitle = "View Sales Order";
public const string PushNotificationMessageBody = "Tap to view Sales Order # ";
}
}

Related

show popup message from a service

I'm trying to create a handler that will produce a popup when a message is received from SignalR which is running in a service. I've got this working in a non-service, but it won't work in a service.
This code works from a non-service:
client.OnMessageReceived +=
(sender2, message) =>
RunOnUiThread(() =>
showMessage(message));
Where client is the SignalR client and showMessage is the method called when a message is received from client. No problem.
Now I want to run the client in/as a service and I need to wire up a handler to do basically the same thing. I've tried several methods I found on StackOverflow and other sites, but all the stuff out there is java, not c# for Xamarin for Visual Studio (2017) and does not translate well. I'm at a loss as to how to proceed.
* Update *
This is my forground service code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Acr.UserDialogs;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Support.V4.App;
using Android.Views;
using Android.Widget;
using ChatClient.Shared;
using Java.Lang;
namespace OML_Android
{
public class SignalRService : Service
{
public const int SERVICE_RUNNING_NOTIFICATION_ID = 10000;
public const string ACTION_MAIN_ACTIVITY = "OML_Android.action.MainActivity";
public const string SERVICE_STARTED_KEY = "has_service_been_started";
bool isStarted;
Handler handler;
Action runnable;
// This information will eventually be pulled from the intent, this is just for testing
public string firstname = "";
public string lastname = "";
public string username = "";
public string name = "";
private Client mInstance;
public override IBinder OnBind(Intent intent)
{
return null;
// throw new NotImplementedException();
}
public override void OnCreate()
{
base.OnCreate();
mInstance = Client.Getinstance(name, username, firstname, lastname);
mInstance.Connect();
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
return StartCommandResult.Sticky;
}
public override void OnDestroy()
{
// Not sure what to do here yet, got to get the service working first
}
void RegisterForegroundService()
{
var notification = new NotificationCompat.Builder(this)
.SetContentTitle(Resources.GetString(Resource.String.app_name))
.SetContentText(Resources.GetString(Resource.String.notification_text))
.SetSmallIcon(Resource.Drawable.alert_box)
.SetContentIntent(BuildIntentToShowMainActivity())
.SetOngoing(true)
//.AddAction(BuildRestartTimerAction())
//.AddAction(BuildStopServiceAction())
.Build();
// Enlist this instance of the service as a foreground service
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
}
PendingIntent BuildIntentToShowMainActivity()
{
var notificationIntent = new Intent(this, typeof(MainActivity));
notificationIntent.SetAction(ACTION_MAIN_ACTIVITY);
notificationIntent.SetFlags(ActivityFlags.SingleTop | ActivityFlags.ClearTask);
notificationIntent.PutExtra(SERVICE_STARTED_KEY, true);
var pendingIntent = PendingIntent.GetActivity(this, 0, notificationIntent, PendingIntentFlags.UpdateCurrent);
return pendingIntent;
}
public async void showMessage(string message)
{
var result = await UserDialogs.Instance.ConfirmAsync(new ConfirmConfig
{
Message = "Text Message from Company: " + System.Environment.NewLine + message,
OkText = "Ok",
});
if (result)
{
// do something
var x = message;
}
}
}
}
This service sets the client to run as a foreground service (I assume), the client code is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Client;
namespace ChatClient.Shared
{
public sealed class Client
{
//public string username;
private string username = "";
private string _platform = "";
private readonly HubConnection _connection;
private readonly IHubProxy _proxy;
public string _Username
{
get { return username; }
set { username = value; }
}
public string _Platform
{
get { return _platform; }
set { _platform = value; }
}
public event EventHandler<string> OnMessageReceived;
public static Client instance = null;
public Client(string name, string username, string firstname, string lastname, string company, string department, string section)
{
_Username = username;
_Platform = name;
_platform = _Platform;
Dictionary<string, string> queryString = new Dictionary<string, string>();
queryString.Add("username", username);
queryString.Add("firstname", firstname);
queryString.Add("lastname", lastname);
queryString.Add("company", company);
queryString.Add("department", department);
queryString.Add("section", section);
_connection = new HubConnection("https://www.example.com/SignalRhub",queryString );
_proxy = _connection.CreateHubProxy("chathub");
}
public static Client Getinstance(string name, string username, string firstname, string lastname)
{
// create the instance only if the instance is null
if (instance == null)
{
// The username and user's name are set before instantiation
instance = new Client(name, username, firstname, lastname,"","","");
}
// Otherwise return the already existing instance
return instance;
}
public async Task Connect()
{
await _connection.Start(); //.ContinueWith._connection.server.registerMeAs("");
_proxy.On("broadcastMessage", (string platform, string message) =>
{
if (OnMessageReceived != null)
OnMessageReceived(this, string.Format("{0}: {1}", platform, message));
});
// Send("Connected");
}
public async Task<List<string>> ConnectedUsers()
{
List<string> Users = await _proxy.Invoke<List<string>>("getConnectedUsers");
return Users;
}
public async Task<List<string>> ConnectedSectionUsers(string company, string department, string section, string username)
{
List<string> Users = await _proxy.Invoke<List<string>>("getConnectedSectionUsers",company, department, section, username);
return Users;
}
public Task Send(string message)
{
return _proxy.Invoke("Send", _platform, message);
}
public Task SendSectionMessage(string company, string department, string section, string name, string message)
{
return _proxy.Invoke("messageSection", company, department, section, name, message);
}
public Task SendCompanyMessage(string company, string department, string section, string name, string message)
{
return _proxy.Invoke("messageCompany", company, name, message);
}
}
}
This is the code I plan to use to start the service (not working yet), I will be adding information to the intent via intent.PutExtras, namely Firstname, lastname, username, name, company, department and section. For now I just set them to null string in the service for testing purposes.:
public static void StartForegroundServiceComapt<SignalRService>(this Context context, Bundle args = null) where SignalRService : Service
{
var intent = new Intent(context, typeof(SignalRService));
if (args != null)
{
intent.PutExtras(args);
}
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
context.StartForegroundService(intent);
}
else
{
context.StartService(intent);
}
}
This all works as expected, I still need to do some work to clean it up but it is working. I am able to connect to the server hub and send messages to the correct groups of people. Now I need to get this to run as a foreground service.
Is your app creating a notification channel? You should be passing the notification channel ID to the NotificationCompat.Builder constructor.
It doesn't look like you're calling the RegisterForegroundService method to promote the service to a foreground service. You'll want to call RegisterForegroundService early in the OnCreate override. Modern versions of Android require a foreground service to show a notification within a few seconds or an exception will be thrown.
You may want to add the android.permission.FOREGROUND_SERVICE permission to your Android Manifest because it's required on Android P and later.
I don't think ACR.UserDialogs will work if your app has no current top activity. The service outlives the activity so it's very possible to run into this scenario. You can simply have the service update the existing foreground notification to show the user a new message is available.

How do I intercept a message in FormFlow before it reaches recognizers? (enum usage)

I would like to intercept what the user writes if he doesn't like any option in the list. My code is the following, but the validate function works only if the user chooses an option.
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace BotApplication.App_Code
{
public enum MainOptions { AccessoAreaRiservata = 1, AcquistoNuovaPolizza, RinnovoPolizza, Documenti, StatoPratica, AltroArgomento }
[Serializable]
public class MainReq
{
[Prompt("Indicare la tipologia della richiesta? {||}")]
public MainOptions? MainOption;
public static IForm<MainReq> BuildForm()
{
var form = (new FormBuilder<MainReq>()
.Field(nameof(MainOption),validate: async (state, response) =>
{
var result = new ValidateResult { IsValid = true };
{
string risposta = (response.ToString());
if (risposta == "AltroArgomento")
{
result.Feedback = "it works only if user choose an option";
result.IsValid = true;
}
return result;
}
})
.Build());
return form;
}
}
}
There are a few possible workarounds for you to consider. Normally if you want to account for situations where a user wants to ask a question or say something unrelated to the form, you'd have them cancel the form using the Quit command. If you want your bot to be smart enough to interpret when users change the subject in the middle of a form, that's a bit more advanced.
If you want to keep using a validate method, you can change your MainOption field to a string instead of a MainOptions? so that all user input gets sent to the validate method, but then you'd need to generate the list of choices yourself.
My recommendation is to use a custom prompter instead of a validate method. I've written a blog post that details how to make such a prompter. First you would provide a NotUnderstood template to indicate to your prompter when a message isn't a valid option in FormFlow. Then in the prompter you would call your QnAMaker dialog or do whatever you want with the message.
// Define your NotUnderstood template
[Serializable, Template(TemplateUsage.NotUnderstood, NOT_UNDERSTOOD)]
public class MainReq
{
public const string NOT_UNDERSTOOD = "Not-understood message";
[Prompt("Indicare la tipologia della richiesta? {||}")]
public MainOptions? MainOption;
public static IForm<MainReq> BuildForm()
{
var form = (new FormBuilder<MainReq>()
.Prompter(PromptAsync) // Build your form with a custom prompter
.Build());
return form;
}
private static async Task<FormPrompt> PromptAsync(IDialogContext context, FormPrompt prompt, MainReq state, IField<MainReq> field)
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
// Here is where we've made a change to the default prompter.
if (promptMessage.Text == NOT_UNDERSTOOD)
{
// Access the message the user typed with context.Activity
await context.PostAsync($"Do what you want with the message: {context.Activity.AsMessageActivity()?.Text}");
}
else
{
await context.PostAsync(promptMessage);
}
return prompt;
}
}

Error using QnAMaker sample with feedback

I have been trying to use Microsoft Cognitive and AI toolkit with QnAMaker API, in order to create a simplistic chat bot.
While my normal qnaMakerAi chat bot works fine, there is an issue while I was trying to enhance it's feature and include the bot feedback within the response.
I have been following the exact code sample as is referred here.
The issue I'm having is:
Exception: Object reference not set to an instance of an object.
[File of type 'text/plain'].
The debugger is giving error in the code section - (in the file WebApiConfig.cs)
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
};
I have also raised a detailed description of the issue in - https://github.com/Microsoft/BotBuilder/issues/4267.
Please check and suggest.
Based on the user comments, here is the code for MessagesController -
using System;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.Dialogs;
using System.Web.Http.Description;
using System.Net.Http;
using QnABot.Dialogs;
namespace Microsoft.Bot.Sample.QnABot
{
[BotAuthentication]
public class MessagesController : ApiController
{
/// <summary>
/// POST: api/Messages
/// receive a message from a user and send replies
/// </summary>
/// <param name="activity"></param>
[ResponseType(typeof(void))]
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
// check if activity is of type message
if (activity.GetActivityType() == ActivityTypes.Message)
{
//await Conversation.SendAsync(activity, () => new RootDialog());
await Conversation.SendAsync(activity, () => new QnaDialog());
}
else
{
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
}
For QnADialog -
using Microsoft.Bot.Builder.Azure;
using Microsoft.Bot.Builder.CognitiveServices.QnAMaker;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace QnABot.Dialogs
{
[Serializable]
public class QnaDialog : QnAMakerDialog
{
public QnaDialog() : base(new QnAMakerService(new QnAMakerAttribute("b372e477-0a2f-4a5a-88d5-3a664d16a4c3", "4ee02ead3xxxxxx", "Sorry, I couldn't find an answer for that", 0.5)))
{
}
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
// answer is a string
var answer = result.Answers.First().Answer;
Activity reply = ((Activity)context.Activity).CreateReply();
string[] qnaAnswerData = answer.Split(';');
int dataSize = qnaAnswerData.Length;
string title = qnaAnswerData[0];
string description = qnaAnswerData[1];
string url = qnaAnswerData[2];
string imageURL = qnaAnswerData[3];
HeroCard card = new HeroCard
{
Title = title,
Subtitle = description,
};
card.Buttons = new List<CardAction>
{
new CardAction(ActionTypes.OpenUrl, "Learn More", value: url)
};
card.Images = new List<CardImage>
{
new CardImage( url = imageURL)
};
reply.Attachments.Add(card.ToAttachment());
await context.PostAsync(reply);
}
protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
// get the URL
var answer = result.Answers.First().Answer;
string[] qnaAnswerData = answer.Split(';');
string qnaURL = qnaAnswerData[2];
// pass user's question
var userQuestion = (context.Activity as Activity).Text;
context.Call(new FeedbackDialog(qnaURL, userQuestion), ResumeAfterFeedback);
}
private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<IMessageActivity> result)
{
if (await result != null)
{
await MessageReceivedAsync(context, result);
}
else
{
context.Done<IMessageActivity>(null);
}
}
}
}
For FeedBackDialog -
using Microsoft.ApplicationInsights;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace QnABot.Dialogs
{
[Serializable]
public class FeedbackDialog : IDialog<IMessageActivity>
{
private string qnaURL;
private string userQuestion;
public FeedbackDialog(string url, string question)
{
// keep track of data associated with feedback
qnaURL = url;
userQuestion = question;
}
public async Task StartAsync(IDialogContext context)
{
var feedback = ((Activity)context.Activity).CreateReply("Did you find what you need?");
feedback.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new CardAction(){ Title = "đź‘Ť", Type=ActionTypes.PostBack, Value=$"yes-positive-feedback" },
new CardAction(){ Title = "đź‘Ž", Type=ActionTypes.PostBack, Value=$"no-negative-feedback" }
}
};
await context.PostAsync(feedback);
context.Wait(this.MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var userFeedback = await result;
if (userFeedback.Text.Contains("yes-positive-feedback") || userFeedback.Text.Contains("no-negative-feedback"))
{
// create telemetry client to post to Application Insights
TelemetryClient telemetry = new TelemetryClient();
if (userFeedback.Text.Contains("yes-positive-feedback"))
{
// post feedback to App Insights
var properties = new Dictionary<string, string>
{
{"Question", userQuestion },
{"URL", qnaURL },
{"Vote", "Yes" }
// add properties relevant to your bot
};
telemetry.TrackEvent("Yes-Vote", properties);
}
else if (userFeedback.Text.Contains("no-negative-feedback"))
{
// post feedback to App Insights
}
await context.PostAsync("Thanks for your feedback!");
context.Done<IMessageActivity>(null);
}
else
{
// no feedback, return to QnA dialog
context.Done<IMessageActivity>(userFeedback);
}
}
}
}
1st, bad config
Ok, the 1st problem is the fact that you inverted 2 parameters in your QnaDialog declaration:
public QnaDialog() : base(new QnAMakerService(new QnAMakerAttribute("b372e477-0a2f-4a5a-88d5-3a664d16a4c3", "4ee02ead3xxxxxx", "Sorry, I couldn't find an answer for that", 0.5)))
The syntax is: Qn
public QnAMakerAttribute(string subscriptionKey, string knowledgebaseId, ...
Here you inverted your Key and your knowledgebaseId. The Guid should be in 2nd position, not 1st.
Note that I modified your subscription key in the question and reply, you should note share them like that.
Code improvement
The sample that you used seems to be not valid:
in the case there is no match
when your answer is not made of a string with ; separator (like when you type "hi", the reply is "hello"
I added some security to avoid errors in those cases:
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
// answer is a string
var answer = result.Answers.First().Answer;
Activity reply = ((Activity)context.Activity).CreateReply();
var qnaAnswerData = answer.Split(';');
var dataSize = qnaAnswerData.Length;
if (dataSize == 3)
{
var title = qnaAnswerData[0];
var description = qnaAnswerData[1];
var url = qnaAnswerData[2];
var imageUrl = qnaAnswerData[3];
var card = new HeroCard
{
Title = title,
Subtitle = description,
Buttons = new List<CardAction>
{
new CardAction(ActionTypes.OpenUrl, "Learn More", value: url)
},
Images = new List<CardImage>
{
new CardImage(url = imageUrl)
},
};
reply.Attachments.Add(card.ToAttachment());
}
else
{
reply.Text = answer;
}
await context.PostAsync(reply);
}
protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
if (result.Answers.Count > 0)
{
// get the URL
var answer = result.Answers.First().Answer;
var qnaAnswerData = answer.Split(';');
var dataSize = qnaAnswerData.Length;
if (dataSize == 3)
{
var qnaUrl = qnaAnswerData[2];
// pass user's question
var userQuestion = (context.Activity as Activity).Text;
context.Call(new FeedbackDialog(qnaUrl, userQuestion), ResumeAfterFeedback);
}
else
{
await ResumeAfterFeedback(context, new AwaitableFromItem<IMessageActivity>(null));
}
}
else
{
await ResumeAfterFeedback(context, new AwaitableFromItem<IMessageActivity>(null));
}
}
private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<IMessageActivity> result)
{
if (await result != null)
{
await MessageReceivedAsync(context, result);
}
else
{
context.Done<IMessageActivity>(null);
}
}

Have I set up my Android push notifications correctly?

I am setting up push notifications in my Xamarin Forms app for my Android project. I have been following the online documentation (https://developer.xamarin.com/guides/xamarin-forms/cloud-services/push-notifications/azure/) to get it working but I am having some problems
1 - The notifications don't arrive when I send a test from my Azure Notification Hub.
2 - The app crashes during debug after a certain amount of time during the Register method.
I believe that the problem is being caused by its registration, I don't think the registration is working and hence the app times out which causes it to crash and also the Azure Notification Hub sends the message successfully but no device picks it up (possibly because it's not registred)
To start off, here is what I have done so far.
I have setup a project in console.firebase.google.com and taken note of the sender ID and the Server Key as instructed. I have given the Project ID a name of myapp-android-app.
I have added all the code from the documtation to my Android project.
I have added a GCM notification service to my Notification Hub in Azure and supplied it with the Server Key documented from my Firebase app registration.
I have added my google account to my Andoird Emulator
Here is the source code from my Android project.
MainActivity.cs
using System;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Gcm.Client;
namespace MyApp.Droid
{
[Activity(Label = "#string/app_name", Theme = "#style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
//public static MainActivity CurrentActivity { get; private set; }
// Create a new instance field for this activity.
static MainActivity instance = null;
// Return the current activity instance.
public static MainActivity CurrentActivity
{
get
{
return instance;
}
}
protected override void OnCreate(Bundle bundle)
{
instance = this;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();
LoadApplication(new App());
try
{
// Check to ensure everything's setup right
GcmClient.CheckDevice(this);
GcmClient.CheckManifest(this);
// Register for push notifications
System.Diagnostics.Debug.WriteLine("Registering...");
GcmClient.Register(this, PushHandlerBroadcastReceiver.SENDER_IDS);
}
catch (Java.Net.MalformedURLException)
{
CreateAndShowDialog("There was an error creating the client. Verify the URL.", "Error");
}
catch (Exception e)
{
CreateAndShowDialog(e.Message, "Error");
}
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
}
void CreateAndShowDialog(String message, String title)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetMessage(message);
builder.SetTitle(title);
builder.Create().Show();
}
}
}
GcmService.cs
using Android.App;
using Android.Content;
using Android.Media;
using Android.Support.V4.App;
using Android.Util;
using Gcm.Client;
using Microsoft.WindowsAzure.MobileServices;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
[assembly: Permission(Name = "#PACKAGE_NAME#.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "#PACKAGE_NAME#.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "com.google.android.c2dm.permission.RECEIVE")]
[assembly: UsesPermission(Name = "android.permission.INTERNET")]
[assembly: UsesPermission(Name = "android.permission.WAKE_LOCK")]
//GET_ACCOUNTS is only needed for android versions 4.0.3 and below
[assembly: UsesPermission(Name = "android.permission.GET_ACCOUNTS")]
namespace MyApp.Droid
{
[BroadcastReceiver(Permission = Gcm.Client.Constants.PERMISSION_GCM_INTENTS)]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_MESSAGE }, Categories = new string[] { "#PACKAGE_NAME#" })]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_REGISTRATION_CALLBACK }, Categories = new string[] { "#PACKAGE_NAME#" })]
[IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_LIBRARY_RETRY }, Categories = new string[] { "#PACKAGE_NAME#" })]
public class PushHandlerBroadcastReceiver : GcmBroadcastReceiverBase<GcmService>
{
public static string[] SENDER_IDS = new string[] { "xxxxxxxxxx" };
}
[Service]
public class GcmService : GcmServiceBase
{
public static string RegistrationToken { get; private set; }
public GcmService()
: base(PushHandlerBroadcastReceiver.SENDER_IDS) { }
protected override void OnRegistered(Context context, string registrationToken)
{
Log.Verbose("PushHandlerBroadcastReceiver", "GCM Registered: " + registrationToken);
RegistrationToken = registrationToken;
var push = AzureService.DefaultManager.CurrentClient.GetPush();
MainActivity.CurrentActivity.RunOnUiThread(() => Register(push, null));
}
protected override void OnUnRegistered(Context context, string registrationToken)
{
Log.Error("PushHandlerBroadcastReceiver", "Unregistered RegisterationToken: " + registrationToken);
}
protected override void OnError(Context context, string errorId)
{
Log.Error("PushHandlerBroadcastReceiver", "GCM Error: " + errorId);
}
public async void Register(Microsoft.WindowsAzure.MobileServices.Push push, IEnumerable<string> tags)
{
try
{
const string templateBodyGCM = "{\"data\":{\"message\":\"$(messageParam)\"}}";
JObject templates = new JObject();
templates["genericMessage"] = new JObject
{
{"body", templateBodyGCM}
};
await push.RegisterAsync(RegistrationToken, templates);
Log.Info("Push Installation Id", push.InstallationId.ToString());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message + ex.StackTrace);
Debugger.Break();
}
}
protected override void OnMessage(Context context, Intent intent)
{
Log.Info("PushHandlerBroadcastReceiver", "GCM Message Received!");
var msg = new StringBuilder();
if (intent != null && intent.Extras != null)
{
foreach (var key in intent.Extras.KeySet())
msg.AppendLine(key + "=" + intent.Extras.Get(key).ToString());
}
// Retrieve the message
var prefs = GetSharedPreferences(context.PackageName, FileCreationMode.Private);
var edit = prefs.Edit();
edit.PutString("last_msg", msg.ToString());
edit.Commit();
string message = intent.Extras.GetString("message");
if (!string.IsNullOrEmpty(message))
{
CreateNotification("New todo item!", "Todo item: " + message);
}
string msg2 = intent.Extras.GetString("msg");
if (!string.IsNullOrEmpty(msg2))
{
CreateNotification("New hub message!", msg2);
return;
}
CreateNotification("Unknown message details", msg.ToString());
}
void CreateNotification(string title, string desc)
{
// Create notification
var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;
// Create an intent to show the UI
var uiIntent = new Intent(this, typeof(MainActivity));
// Create the notification
// we use the pending intent, passing our ui intent over which will get called
// when the notification is tapped.
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
var notification = builder.SetContentIntent(PendingIntent.GetActivity(this, 0, uiIntent, 0))
.SetSmallIcon(Android.Resource.Drawable.SymActionEmail)
.SetTicker(title)
.SetContentTitle(title)
.SetContentText(desc)
.SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification)) // set the sound
.SetAutoCancel(true).Build(); // remove the notification once the user touches it
// Show the notification
notificationManager.Notify(1, notification);
}
}
}
I replaced the string in: public static string[] SENDER_IDS = new string[] { "xxxxxxxx" }; with the Sender id I recorded from my Firebase console app.
When I run the app through my Android Emulator I get the following error after about 3 minutes.
Error in GcmService.cs:
Exception: System.Threading.Tasks.TaskCanceledException: A task was canceled.
seen at Line 71:
await push.RegisterAsync(RegisteToken, templates);
Does anyone know what I have done wrong? Am I correct in thinking that it's not registering correctly?
I was able to integrate Firebase Cloud Messaging in Xamarin.Forms (both Android and iOS) following this official Xamarin guide that is well explained:
https://developer.xamarin.com/guides/android/application_fundamentals/notifications/remote-notifications-with-fcm/
Looking at your code, it seems to me that you use the old Gcm service (that is deprecated) and not the new Fcm.
You should have a google-services.json file and a FirebaseMessagingService class
Then...
Note
As of now, Xamarin Forms does not handle very well the deployment of the app while developing, making a dirty reinstallation that seems to invalidate the previously obtained Notification Token and sometimes raising IllegalStateException when calling Fcm library's methods.
That's a known bug that can be resolved by cleaning the solution and
relaunching

Twilio Rest API Helper Library, v 5.0.1, C# - MessageResource.Create function call not returning properly

I am using the Twilio REST API helper library, v 5.0.1 in my C# ASP.NET MVC Web Application. I created the following helper class and function to send out text messages:
using MyApplication.Web.Helpers;
using System;
using System.Configuration;
using Twilio;
using Twilio.Exceptions;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
namespace MyApplication.Web.Services
{
public class TwilioSmsSender : ISmsSender
{
public string AccountSid { get; set; }
public string AuthToken { get; set; }
public string FromPhoneNumber { get; set; }
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public string SmsPrefix { get; set; }
public string SmsSuffix { get; set; }
public TwilioSmsSender()
{
//get our Twilio account info from the config file
AccountSid = ConfigurationManager.AppSettings["TwilioAccountSid"];
AuthToken = ConfigurationManager.AppSettings["TwilioAuthToken"];
FromPhoneNumber = ConfigurationManager.AppSettings["SmsService.FromPhoneNumber"];
SmsPrefix = ConfigurationManager.AppSettings["SmsPrefix"];
SmsSuffix = ConfigurationManager.AppSettings["SmsSuffix"];
if (FromPhoneNumber.Length == 10)
{
FromPhoneNumber = $"+1{FromPhoneNumber}";
}
TwilioClient.Init(AccountSid, AuthToken);
}
public INotificationResponse SendTextMessage(string phoneNumber, string message, bool useFormatting = true)
{
var resp = new TwilioSmsSenderResponse();
resp.Succeeded = false;
resp.AttemptDateTimeUtc = DateTime.UtcNow;
if (useFormatting)
{
message = $"{SmsPrefix}{message}{SmsSuffix}";
}
try
{
var msgResponse = MessageResource.Create(
to: new PhoneNumber($"+1{phoneNumber}"),
from: new PhoneNumber($"{FromPhoneNumber}"),
body: message);
//Previous line works (i.e, I get the text message that I'm sending out successfully).
//However, none of the following lines are running...
//As you see, this is in a try/catch block... and it doesn't go to the catch block either!
if (msgResponse.ErrorCode == null)
{
//successfully queued
resp.Succeeded = true;
resp.ReferenceId = msgResponse.Sid;
resp.AttemptDateTimeUtc = DateTime.UtcNow;
}
else
{
//Twilio sent an error back
log.Info($"Twilio sent an error back: {msgResponse}");
resp.Succeeded = false;
resp.Notes = $"ErrorCode: {msgResponse.ErrorCode}, ErrorMessage: {msgResponse.ErrorMessage}";
resp.AttemptDateTimeUtc = DateTime.UtcNow;
}
}
catch (Exception e)
{
resp.Succeeded = false;
resp.Notes = ExceptionsHelper.GetExceptionDetailsAsString(e);
resp.AttemptDateTimeUtc = DateTime.UtcNow;
log.Error($"Twilio Error: {resp.Notes}, to: {phoneNumber}, message: {message}");
}
return resp;
}
}
}
Unfortunately, my code is not behaving as I expected it would after the MessageResource.Create() call. That is, the text-message is sent out correctly and I receive the SMS on my phone. However, I expect the call to return control to my msgResponse variable and I expect the
if (msgResponse.ErrorCode == null) ...
line and subsequent lines to run but that is not happening. I can put a breakpoint on the var msgResponse line and it will run that just fine but it does not run any code lines after that. You’ll see that I have the call in a try/catch. I suspected there was an exception that was occurring but it doesn’t seem so because it doesn’t go to my catch block either! The text message is being sent successfully! All I want to do is to get an acknowledgement back so that I can properly log it and send that information back to the routines that are calling this function.
Any help would be greatly appreciated!
version 5.0.2 fixed this for me just update twilio to 5.0.2. they just added .ConfigureAwait(false); with CreateAsync

Categories