I have a cross platform XF app.
I am using Toast.Forms for sending local app notifications, the plugin works on both iOS and Android.
On iOS it works perfectly fine, but on Android it works only for versions lower than 7.1, for oreo 8.0 or 8.1 (> API 26) it does not work anymore.
Here is my toast class:
internal class ToastService : IToastService
{
private readonly IAppDeviceDependency _DeviceDependency;
public ToastService([NotNull] IAppDeviceDependency deviceDependency)
{
_DeviceDependency = deviceDependency ?? throw new ArgumentNullException(nameof(deviceDependency));
}
public void ShowToast(string title, string description, bool isClickable, bool clearFromHistory)
{
DeviceToastProvider.ShowToast(title, description, isClickable, clearFromHistory);
}
private IToastProvider DeviceToastProvider => _DeviceDependency.Get<IToastProvider>();
}
Android toast provider:
[assembly: Dependency(typeof(AndroidToastProvider))]
namespace MyApp.Droid.Providers
{
public class AndroidToastProvider : IToastProvider
{
public async void ShowToast(string title, string description, bool isClickable, bool clearFromHistory)
{
var notificator = DependencyService.Get<IToastNotificator>();
await notificator.Notify(new NotificationOptions
{
Title = title,
Description = description,
IsClickable = isClickable,
ClearFromHistory = clearFromHistory
});
}
}
}
Apple toast provider
[assembly: Dependency(typeof(AppleToastProvider))]
namespace MyApp.iOS.Providers
{
public class AppleToastProvider : IToastProvider
{
public async void ShowToast(string title, string description, bool isClickable, bool clearFromHistory)
{
var notificator = DependencyService.Get<IToastNotificator>();
await notificator.Notify(new NotificationOptions
{
Title = title,
Description = description,
IsClickable = isClickable,
ClearFromHistory = clearFromHistory
});
}
}
}
Android 8 requires notifications to have a channel assigned to them to be displayed (see more here: https://developer.android.com/guide/topics/ui/notifiers/notifications). If your plugin doesn't support that you can ask the authors to update it in a proper way or you can create your own code using native APIs.
Found the solution to the problem. Apparently I was compiling using Android version 7.1, which does not support the newly Notification Channel of the Android Oreo 8.0. Now I am compiling using Android 8.1 (Oreo), I have updated Toast.plugin to the latest version and it works perfectly fine on all versions lowers than Android 8.1
More on the issue can be found here: https://learn.microsoft.com/en-us/xamarin/android/app-fundamentals/notifications/local-notifications
Related
I am trying to verify that the UI elements of my LoginPage work as expected. The test is supposed to write a user name, user password and server adress into the corresponding entry fields then tap the sign in button.
The UITest loads the LoginPage and then fails with this exception:
Nachricht:
System.Net.WebException : POST Failed
Stapelüberwachung:
HttpClient.HandleHttpError(String method, Exception exception, ExceptionPolicy exceptionPolicy)
HttpClient.SendData(String endpoint, String method, HttpContent content, ExceptionPolicy exceptionPolicy, Nullable`1 timeOut)
HttpClient.Post(String endpoint, String arguments, ExceptionPolicy exceptionPolicy, Nullable`1 timeOut)
HttpApplicationStarter.Execute(String intentJson)
AndroidAppLifeCycle.LaunchApp(String appPackageName, ApkFile testServerApkFile, Int32 testServerPort)
AndroidApp.ctor(IAndroidAppConfiguration appConfiguration, IExecutor executor)
AndroidAppConfigurator.StartApp(AppDataMode appDataMode)
ReplDroid.ConnectToDeviceAndr() Zeile 33
ReplDroid.BeforeEachTest() Zeile 24
Now the really interesting part is that it worked before, but after I added code which threw and exception (and removed it again) it is stuck in this POST Failed exception.
To fix my problem I already tried: doing a factory reset of the emulator, cleaning the solution and rebuilding it, deleting the .vs folder, restarting vs and the emulator, testing on different emulator instances and restarting the entire PC. Nothing worked reliably.
The code of my test project is below:
using NUnit.Framework;
using System;
using System.Threading;
using Xamarin.UITest;
namespace REPL
{
[TestFixture(Platform.Android)]
public class ReplDroid
{
IApp app;
Platform platform;
LoginPageController loginPageController;
MainMenuController mainMenuController;
public ReplDroid(Platform platform)
{
this.platform = platform;
}
[SetUp]
public void BeforeEachTest()
{
ConnectToDeviceAndr();
loginPageController = new LoginPageController(app);
mainMenuController = new MainMenuController(app);
}
private void ConnectToDeviceAndr()
{
app = ConfigureApp.Android
.ApkFile(ApkLocation)
.PreferIdeSettings()
.EnableLocalScreenshots()
.StartApp();
}
[Test]
public void Login()
{
loginPageController.EnterUserNumber(UserNumber);
loginPageController.EnterPassword(Password);
loginPageController.EnterServerUrl(ServerUrl);
loginPageController.SignIn();
}
}
}
using Xamarin.UITest;
using Xamarin.UITest.Queries;
namespace REPL
{
public class LoginPageController
{
private readonly IApp app;
private const string UserEntryAutomationId = "UserEntry";
private const string PasswordEntryAutomationId = "PasswordEntry";
private const string ServerUrlEntryAutomationId = "ServerUrlEntry";
private const string LoginButtonAutomationId = "LoginButton";
public LoginPageController(IApp app)
{
this.app = app;
}
public AppResult GetUserEntry() => app.GetByAutomationId(UserEntryAutomationId);
public AppResult GetPasswordEntry() => app.GetByAutomationId(PasswordEntryAutomationId);
public AppResult GetServerUrlEntry() => app.GetByAutomationId(ServerUrlEntryAutomationId);
public AppResult GetLoginButton() => app.GetByAutomationId(LoginButtonAutomationId);
public void EnterUserNumber(string userNumber) => app.WaitAndContinue(UserEntryAutomationId).EnterText(userNumber);
public void EnterPassword(string password) => app.WaitAndContinue(PasswordEntryAutomationId).EnterText(password);
public void EnterServerUrl(string serverUrl) => app.WaitAndContinue(ServerUrlEntryAutomationId).EnterText(serverUrl);
public void SignIn()
{
app.DismissKeyboard();
app.WaitAndContinue(LoginButtonAutomationId).Tap();
}
}
}
All similar posts I could find were about failing to test on App Center, if I somehow missed one relevant to my problem please point me in the right direction.
I had the same problem.
running an empty android Xamarin forms UiTest in my IDE (Jetbrains Rider)
[Test]
public void WelcomeTextIsDisplayed()
{
AppResult[] results = app.WaitForElement(c => c.Marked("Welcome to Xamarin.Forms!"));
app.Screenshot("Welcome screen.");
Assert.IsTrue(results.Any());
}
I fixed it by updating the Nudget package Xamarin.UiTests from 2.2.4 to the newest (3.2.0 currently)
I have been following a youtube tutorial on connecting my a xamarin forms app to an asp.net web api. Unfortunately my Listview is not getting populated by data from the api.
The Xamarin forms app has the following Files:
RestClient.cs
public class RestClient<T> {
private const string WebServiceUrl = "http://localhost:49864/api/Oppotunities/";
public async Task<List<T>> GetAsync()
{
var httpClient = new HttpClient();
var json = await httpClient.GetStringAsync(WebServiceUrl);
var OppotunityList = JsonConvert.DeserializeObject<List<T>>(json);
return OppotunityList ;
} }
MainViewModel.cs
public MainViewModel()
{
InitializeDataAsync();
}
private async Task InitializeDataAsync()
{
var oppotunitiesServices = new OppotunitiesServices();
OppotunitiesList = await oppotunitiesServices.GetOppotunitiesAsync();
}
OppotunityServices.cs
public class OppotunitiesServices
{
public async Task<List<Oppotunity>> GetOppotunitiesAsync()
{
RestClient<Oppotunity> restClient = new RestClient<Oppotunity >();
var oppotunitiesList = await restClient.GetAsync();
return oppotunitiesList;
}
}
If you are debugging from an emulator, you should not use localhost to reach your development machine. You have to use the IP address or your running service.
You can test IP addresses directly from the browser of your emulator so you don't waste time starting/stopping your app to debug this...
Hope it helps
I'm trying to whitelist my app on Android 6.0 or greater. I have seen Android code to do this, but it doesn't translate in Xamarin and Xamarin documentation only tells you that SetAction takes a string as an argument, and then a link to Android documentation which doesn't end up being the same.
Here's the Android code that Xamarin will not accept
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
It doesn't like Settings. I've read the Xamarin documentation which says that SetAction() takes a string arg, and that's all they say and point you to Android documentation.
Note, I am calling this in a javascript interface class and I tried this but it doesn't work
class MyJSInterface : Java.Lang.Object
{
Context context;
public MyJSInterface(Context context)
{
this.context = context;
}
[Export]
[JavascriptInterface]
public void SetDozeOptimization()
{
Toast.MakeText(context, "launch optimization", ToastLength.Short).Show();
setDozeComplete = false;
Intent intent = new Intent();
String packageName = context.PackageName;
PowerManager pm = (PowerManager)context.GetSystemService(Context.PowerService);
if (pm.IsIgnoringBatteryOptimizations(packageName))
intent.SetAction("ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS");
else
{
intent.SetAction("ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS");
intent.SetData(Android.Net.Uri.Parse("package:" + packageName));
}
context.StartActivity(intent);
}
}
So what is the correct syntax to accomplish this?
Android.Provider.Settings.ActionRequestIgnoreBatteryOptimizations:
android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
Android.Provider.Settings.ActionIgnoreBatteryOptimizationSettings:
android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS
Example:
intent.SetAction(Android.Provider.Settings.ActionRequestIgnoreBatteryOptimizations);
intent.SetAction(Android.Provider.Settings.ActionIgnoreBatteryOptimizationSettings);
Here is the code you are looking for:
Intent intent = new Intent();
String packageName = context.PackageName;
PowerManager pm(PowerManager)Android.App.Application.Context.GetSystemService(Context.PowerService);
if (!pm.IsIgnoringBatteryOptimizations(packageName))
{
intent.SetAction(Android.Provider.Settings.ActionRequestIgnoreBatteryOptimizations);
intent.SetData(Android.Net.Uri.Parse("package:" + packageName));
StartActivity(intent);
}
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
I am trying to load a Web application (Readium) in a webview with Xamarin locally. As a target I have UWP, Android and iOS.
I can not get the index.html page open, I have embedded the web in each of the projects, according to https://developer.xamarin.com/guides/xamarin-forms/user-interface/webview/ but I get a blank page.
have implemented the dependency service for each application such as (UWP)
assembly: Dependency(typeof(BaseUrl))]
namespace WorkingWithWebview.UWP
{
public class BaseUrl : IBaseUrl
{
public string Get()
{
return "ms-appx-web:///";
}
}
}
However, creating a new UWP project (without Xamarin), it works well, using the method NavigateToLocalStreamUri(uri, new StreamUriWinRTResolver()) with
public IAsyncOperation<IInputStream> UriToStreamAsync(Uri uri)
{
if (uri == null)
{
throw new Exception();
}
string path = uri.AbsolutePath;
return GetContent(path).AsAsyncOperation();
}
private async Task<IInputStream> GetContent(string path)
{
try
{
Uri localUri = new Uri("ms-appx:///cloud-reader" + path);
StorageFile f = await StorageFile.GetFileFromApplicationUriAsync(localUri);
IRandomAccessStream stream = await f.OpenAsync(FileAccessMode.Read);
return stream;
}
catch (Exception)
{
throw new Exception("Invalid path");
}
}
In what way would the same be done in Xamarin Forms?
Thanks you.
Finally I achieve to load local content adding a custom render for each platform.
Example (UWP):
[assembly: ExportRenderer(typeof(WebView), typeof(WebViewRenderer))]
namespace DisplayEpub.UWP{
public class CustomWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var customWebView = Element as WebView;
Control.Source = new Uri(string.Format("ms-appx-web:///Assets/pdfjs/web/viewer.html"));
}
}
}
}
I've followed the example of Xamarin docs to display PDF using custom render, targeting 3 platforms. I've tested it on Android and Windows:
https://developer.xamarin.com/recipes/cross-platform/xamarin-forms/controls/display-pdf/