I am new to Xamarin Forms. I am building a chat App with Xamarin Forms and Signalr and I want to make the chat messages persistent just like WhatsApp. I'm using Akavache, but I don't seem to be succeeding. I have this code snippet in my ChatPage ViewModel
if(ChatMessageList.Count != 0)
{
var _messages = BlobCache.LocalMachine.GetObject<ChatMessage>("Messages");
ChatMessage chatMessage = new ChatMessage() { Message = _messages.Subscribe(x =>
chatmessage.Message = x.Message).ToString(), IsOwnMessage = isMe, IsSystemMessage = false,
ActionTime = _messages.Subscribe(x => chatmessage.ActionTime = x.ActionTime).ToString() };
ChatMessageList.Add(chatMessage);
MessagingCenter.Send(this, "SCROLL_BOTTOM");
}
else
{
ChatMessage chatMessage = new ChatMessage() { Message = message, IsOwnMessage = isMe,
IsSystemMessage = false, ActionTime = DateTime.Now.ToString("hh:mm tt") };
ChatMessageList.Add(chatmessage);
BlobCache.LocalMachine.InsertObject<ChatMessage>("Messages", chatMessage);
}
Now the chat messages are in system codes. Are can I go about this, please.
Related
We want to receive change notifications from our user's Outlook calendars. We'd like to limit those notifications to only those calendar items that contain our custom property.
CODE WHICH CREATES CALENDAR EVENT
private static async Task<Event> CreateAppointmentAsync(GraphServiceClient graphClient)
{
var newEvent = new Microsoft.Graph.Event
{
Subject = "Test Calendar Appointmnt",
Start = new DateTimeTimeZone() { TimeZone = TimeZoneInfo.Local.Id, DateTime = "2020-11-21T21:00:00" },
End = new DateTimeTimeZone() { TimeZone = TimeZoneInfo.Local.Id, DateTime = "2020-11-21T22:00:00" },
Location = new Location() { DisplayName = "Somewhere" },
Body = new ItemBody { Content = "Some Random Text" },
};
Microsoft.Graph.Event addedEvent;
try
{
newEvent.SingleValueExtendedProperties = new EventSingleValueExtendedPropertiesCollectionPage();
newEvent.SingleValueExtendedProperties.Add(new SingleValueLegacyExtendedProperty { Id = "String {00020329-0000-0000-C000-000000000046} Name CompanyID", Value = "12345" });
addedEvent = await graphClient.Me.Calendar.Events.Request().AddAsync(newEvent);
}
catch (Exception e)
{
throw e;
}
return addedEvent;
}
THE SUBSCRIPTION CODE SNIPPET
var subscription = new Subscription
{
ChangeType = "created",
NotificationUrl ="<OUR-URL>",
Resource = "me/events/?$filter=singleValueExtendedProperties/any(ep: ep/id eq 'String {00020329-0000-0000-C000-000000000046} Name CompanyID' and ep/value ne null)",
ExpirationDateTime = DateTimeOffset.Parse("2020-11-13T18:23:45.9356913Z"),
ClientState = "custom_data_state",
LatestSupportedTlsVersion = "v1_2"
};
The subscription is created successfully, but notifications are not being sent for those items containing the specific custom property described above.
It turns out I was only capturing the "created" notification. I neglected to add "updated" and "deleted".
I was testing with an event that already existed in my calendar. No events were being fired because I didn't create the subscription to detect updates and deletes.
Here is the corrected subscription:
var subscription = new Subscription
{
ChangeType = "created,updated,deleted",
NotificationUrl ="<OUR-URL>",
Resource = "me/events/?$filter=singleValueExtendedProperties/any(ep: ep/id eq 'String {00020329-0000-0000-C000-000000000046} Name CompanyID' and ep/value ne null)",
ExpirationDateTime = DateTimeOffset.Parse("2020-11-13T18:23:45.9356913Z"),
ClientState = "custom_data_state",
LatestSupportedTlsVersion = "v1_2"
};
I made a bot with bot framework v4, using C#, and it's on a webpage, https://websitebotv2.azurewebsites.net/, if there's only 1 user it works fine but the moment I open it on a new tab it gives a IndexOutOfRangeException when I start the conversation.
What do I need to do to make it work with multiple tabs open?
When my bot stars it creates a waterfall dialog asking the name and greeting the user:
public dialogBotBot(dialogBotAccessors accessors, LuisRecognizer luis, QnAMaker qna)
{
// Set the _accessors
_accessors = accessors ?? throw new ArgumentNullException(nameof(accessors));
// The DialogSet needs a DialogState accessor, it will call it when it has a turn context.
_dialogs = new DialogSet(accessors.ConversationDialogState);
// This array defines how the Waterfall will execute.
var waterfallSteps = new WaterfallStep[] {
NameStepAsync,
NameConfirmStepAsync,
};
// The incoming luis variable is the LUIS Recognizer we added above.
this.Recognizer = luis ?? throw new System.ArgumentNullException(nameof(luis));
// The incoming QnA variable is the QnAMaker we added above.
this.QnA = qna ?? throw new System.ArgumentNullException(nameof(qna));
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
_dialogs.Add(new WaterfallDialog("details", waterfallSteps));
_dialogs.Add(new TextPrompt("name"));
}
Then I will save his name on UserProfile class, which contains the field Name and Context, the Context has the purpose of saving the conversation.
This works the first time, but if I open a new tab or refresh the current tab for a new conversation the bot will fetch the first conversation data.
The Exception is thrown in Startup.cs in:
services.AddBot<dialogBotBot>(options =>
{
options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);
// Catches any errors that occur during a conversation turn and logs them to currently
// configured ILogger.
ILogger logger = _loggerFactory.CreateLogger<dialogBotBot>();
options.OnTurnError = async (context, exception) =>
{
logger.LogError($"Exception caught : {exception}");
await context.SendActivityAsync(exception + "\nSorry, it looks like something went wrong.\n" + exception.Message);
};
// Create and add conversation state.
var conversationState = new ConversationState(dataStore);
options.State.Add(conversationState);
// Create and add user state.
var userState = new UserState(dataStore);
options.State.Add(userState);
});
My onTurnAsync method is:
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
// Handle Message activity type, which is the main activity type for shown within a conversational interface
// Message activities may contain text, speech, interactive cards, and binary or unknown attachments.
// see https://aka.ms/about-bot-activity-message to learn more about the message and other activity types
if (turnContext.Activity.Type == ActivityTypes.Message)
{
//Get the current user profile
userProfile = await _accessors.UserProfile.GetAsync(turnContext, () => new UserProfile(), cancellationToken);
userProfile.Contexto.Add(turnContext.Activity.Text);
foreach (string s in userProfile.Contexto)
await turnContext.SendActivityAsync(s);
// Get the conversation state from the turn context.
var state = await _accessors.CounterState.GetAsync(turnContext, () => new CounterState());
// Bump the turn count for this conversation.
state.TurnCount++;
// Check LUIS model
var recognizerResult = await this.Recognizer.RecognizeAsync(turnContext, cancellationToken);
var topIntent = recognizerResult?.GetTopScoringIntent();
// Get the Intent as a string
string strIntent = (topIntent != null) ? topIntent.Value.intent : "";
// Get the IntentScore as a double
double dblIntentScore = (topIntent != null) ? topIntent.Value.score : 0.0;
// Only proceed with LUIS if there is an Intent
// and the score for the Intent is greater than 95
if (strIntent != "" && (dblIntentScore > 2))
{
switch (strIntent)
{
case "None":
//add the bot response to contexto
await turnContext.SendActivityAsync("Desculpa, não percebi.");
break;
case "Utilities_Help":
//add the bot response to contexto
await turnContext.SendActivityAsync("Quero-te ajudar!\nO que precisas?");
break;
default:
// Received an intent we didn't expect, so send its name and score.
//add the bot response to contexto
await turnContext.SendActivityAsync($"Intent: {topIntent.Value.intent} ({topIntent.Value.score}).");
break;
}
}
else
{
if (userProfile.Name == null)
{
// Run the DialogSet - let the framework identify the current state of the dialog from the dialog stack and figure out what (if any) is the active dialog.
var dialogContext = await _dialogs.CreateContextAsync(turnContext, cancellationToken);
var results = await dialogContext.ContinueDialogAsync(cancellationToken);
// If the DialogTurnStatus is Empty we should start a new dialog.
if (results.Status == DialogTurnStatus.Empty)
{
await dialogContext.BeginDialogAsync("details", null, cancellationToken);
}
}
else
{
var answers = await this.QnA.GetAnswersAsync(turnContext);
if (answers.Any() && answers[0].Score > 0.7)
{
// If the service produced one or more answers, send the first one.
await turnContext.SendActivityAsync(answers[0].Answer + "\n" + state.TurnCount);
}
else
{
var responseMessage = $"Ainda não sei a resposta mas vou averiguar\nPosso-te ajudar com mais alguma coisa?";
String connectionString = "Data Source=botdataserverv1.database.windows.net;" +
"Initial Catalog=botDataBase;" +
"User id=AzureAdmin#botdataserverv1.database.windows.net;" +
"Password=admin_123;";
SqlConnection connection = new SqlConnection(connectionString);
SqlDataAdapter adapter = new SqlDataAdapter();
SqlCommand command;
String sms = turnContext.Activity.Text;
float result = answers[0].Score;
String insertMessage = "insert into Mensagem(texto,contexto,grauCerteza)" +
"values('" + sms + "', 'Falta apurar o contexto' ," + result + ")";
connection.Open();
command = new SqlCommand(insertMessage, connection);
adapter.InsertCommand = new SqlCommand(insertMessage, connection);
adapter.InsertCommand.ExecuteNonQuery();
command.Dispose();
connection.Close();
await turnContext.SendActivityAsync(responseMessage);
}
}
// Save the user profile updates into the user state.
await _accessors.UserState.SaveChangesAsync(turnContext, false, cancellationToken);
// Set the property using the accessor.
await _accessors.CounterState.SetAsync(turnContext, state);
// Save the new turn count into the conversation state.
await _accessors.ConversationState.SaveChangesAsync(turnContext);
}
}
}
Your problem is here:
float result = answers[0].Score;
You have:
// Check to see if we have any answers
if (answers.Any() && answers[0].Score > 0.7)
{
[...]
// This is fine
}
else // else, WE HAVE NO ANSWERS
{
[...]
// At this point, answers is an empty array, so answers[0] throws an IndexOutOfRangeException
float result = answers[0].Score;
The reason this happens on refresh is because the new tab's user uses the same User Id. The bot already knows their name, so doesn't show the dialog, and when it calls await this.Recognizer.RecognizeAsync(turnContext, cancellationToken);, the user hasn't entered anything in the new turnContext, so it returns an empty array.
Sidenote: You can set the userID in WebChat with this:
window.WebChat.renderWebChat(
{
directLine: directLine,
userID: "USER_ID" // Make is use Math.random() or something if you want it to be random for each refresh
},
this.botWindowElement.nativeElement
);
I'm using the Microsoft Bot Framework with C# and I'm trying to create a reply message with an image like so:
IMessageActivity m = Activity.CreateMessageActivity();
var images = doc.DocumentNode.SelectNodes("//img[#src]").ToList();
var src = images[10].GetAttributeValue("src", null);
Attachment att = new Attachment();
att.ContentType = "image";
att.ContentUrl = src;
m.Attachments.Add(att);
await context.PostAsync(m);
context.Wait(MessageReceived);
I know that the image source is being extracted correctly but I still get the error:
Microsoft.Rest.ValidationException
What is the cause of this and how do I fix it?
I expect to have a message with the image attached but instead I just get the default bot code error message.
Edit: this is the exception message - 'ReplyToId' cannot be null.
If you are creating a message using the Activity.CreateMessageActivity() then you need to specify the details of user account(ToId,ToName), bot account(FromId, FromName) and conversation(conservationId, channelId).
eg:
var userAccount = new ChannelAccount(toId,toName);
var botAccount = new ChannelAccount(fromId, fromName);
var connector = new ConnectorClient(new Uri(serviceUrl));
IMessageActivity message = Activity.CreateMessageActivity();
if (!string.IsNullOrEmpty(conversationId) && !string.IsNullOrEmpty(channelId))
{
message.ChannelId = channelId;
}
else
{
conversationId = (await connector.Conversations.CreateDirectConversationAsync( botAccount, userAccount)).Id;
}
message.From = botAccount;
message.Recipient = userAccount;
message.Conversation = new ConversationAccount(id: conversationId);
message.Text = "The text you want to send";
//You can add your attachment here
message.Locale = "en-Us";
await connector.Conversations.SendToConversationAsync((Activity)message);
Looking at your code it looks like you can access the context of the Dialog since you are posting the message using context.PostAsync, so the easier way would be to send a message using the context since the context will already contain the details of user, bot and conversation.
You can do this by using context.MakeMessage()
eg:
IMessageActivity reply = context.MakeMessage();
var images = doc.DocumentNode.SelectNodes("//img[#src]").ToList();
var src = images[10].GetAttributeValue("src", null);
Attachment att = new Attachment();
att.ContentType = "image/png";
att.ContentUrl = src;
reply.Attachments.Add(att);
await context.PostAsync(reply);
context.Wait(MessageReceived);
I want to create a Gatt Server in my Xamarin.Forms app so that other devices can scan for it via bluetooth. I am using this plugin:
https://github.com/aritchie/bluetoothle
This is my code to create a Gatt Server and advertise data:
server = CrossBleAdapter.Current.CreateGattServer();
var service = server.AddService(serviceGuid, true);
var characteristic = service.AddCharacteristic(
characteristicGuid,
CharacteristicProperties.Read |
CharacteristicProperties.Write | CharacteristicProperties.WriteNoResponse,
GattPermissions.Read | GattPermissions.Write
);
var notifyCharacteristic = service.AddCharacteristic
(
notifyCharacteristicGuid,
CharacteristicProperties.Indicate | CharacteristicProperties.Notify,
GattPermissions.Read | GattPermissions.Write
);
IDisposable notifyBroadcast = null;
notifyCharacteristic.WhenDeviceSubscriptionChanged().Subscribe(e =>
{
var #event = e.IsSubscribed ? "Subscribed" : "Unsubcribed";
if (notifyBroadcast == null)
{
notifyBroadcast = Observable
.Interval(TimeSpan.FromSeconds(1))
.Where(x => notifyCharacteristic.SubscribedDevices.Count > 0)
.Subscribe(_ =>
{
Debug.WriteLine("Sending Broadcast");
var dt = DateTime.Now.ToString("g");
var bytes = Encoding.UTF8.GetBytes("SendingBroadcast");
notifyCharacteristic.Broadcast(bytes);
});
}
});
characteristic.WhenReadReceived().Subscribe(x =>
{
var write = "HELLO";
// you must set a reply value
x.Value = Encoding.UTF8.GetBytes(write);
x.Status = GattStatus.Success; // you can optionally set a status, but it defaults to Success
});
characteristic.WhenWriteReceived().Subscribe(x =>
{
var write = Encoding.UTF8.GetString(x.Value, 0, x.Value.Length);
Debug.WriteLine("in WhenWriteReceived() value: " + write);
// do something value
});
await server.Start(new AdvertisementData
{
LocalName = "DariusServer",
ServiceUuids = new List<Guid>() { serverServiceGuid }
});
I am using this app to scan for my advertisement data:
https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp
I can't discover my app with it. I don't know what I'm doing wrong? I am testing with a real device, SM-T350 tablet
I spent countless hours to get this plugin to work with no luck. But this native code works for anyone else who has the same problem:
private async Task AndroidBluetooth()
{
try
{
await Task.Delay(5000); // just to make sure bluetooth is ready to go, this probably isn't needed, but good for peace of mind during testing
BluetoothLeAdvertiser advertiser = BluetoothAdapter.DefaultAdapter.BluetoothLeAdvertiser;
var advertiseBuilder = new AdvertiseSettings.Builder();
var parameters = advertiseBuilder.SetConnectable(true)
.SetAdvertiseMode(AdvertiseMode.Balanced)
//.SetTimeout(10000)
.SetTxPowerLevel(AdvertiseTx.PowerHigh)
.Build();
AdvertiseData data = (new AdvertiseData.Builder()).AddServiceUuid(new ParcelUuid(Java.Util.UUID.FromString("your UUID here"))).Build();
MyAdvertiseCallback callback = new MyAdvertiseCallback();
advertiser.StartAdvertising(parameters, data, callback);
}
catch(Exception e)
{
}
}
public class MyAdvertiseCallback : AdvertiseCallback
{
public override void OnStartFailure([GeneratedEnum] AdvertiseFailure errorCode)
{
// put a break point here, in case something goes wrong, you can see why
base.OnStartFailure(errorCode);
}
public override void OnStartSuccess(AdvertiseSettings settingsInEffect)
{
base.OnStartSuccess(settingsInEffect);
}
}
}
Just to note, it wouldn't work if if I included the device name, because the bluetooth transmission would be too large in that case with a service UUID (max 31 bytes I believe).
I have enabled signin card in my BOT framework to redirect to the client application URL and it worked perfectly find in the BOT emulator and from the WEBCHAT. But i have issues in the Cortana channel.
While clicking on the button, instead of opening a default browser with the client application ur, a popup opens and says 'we cant connect to the service right now. check your network connection or try again'
Here is my code sample in the BOT framework::
public async Task getmypersonalData(IDialogContext context, LuisResult result)
{
string convid = _convid;
ConnectorClient connector = new ConnectorClient(new Uri(_serviceURL));
var replyToConversation = context.MakeMessage();
// Activity replyToConversation = _activity.CreateReply();
replyToConversation.Recipient = new ChannelAccount(id: _fromAddess);// _activity.From;
replyToConversation.Type = "message";
replyToConversation.Attachments = new List<Attachment>();
List<CardAction> cardButtons = new List<CardAction>();
CardAction plButton = new CardAction()
{
Value = $"{System.Configuration.ConfigurationManager.AppSettings["AppWebSite"]}?conversationid={HttpUtility.UrlEncode(convid)}",
Type = "signin",
Title = "Authentication Required"
};
cardButtons.Add(plButton);
SigninCard plCard = new SigninCard("Please login to the application to access this feature", new List<CardAction>() { plButton });
Attachment plAttachment = plCard.ToAttachment();
replyToConversation.Attachments.Add(plAttachment);
await context.PostAsync(replyToConversation);
}