Local notification not triggering in Android 10 - xamarin.forms

I am working on xamarin.forms app and I am setting local notification using alarm manager. It is triggering fine on android version 8 and 9 but somehow it's not triggering in Android 10.
In android device logs for Android 10, I got
07-22 15:00:06.612 Samsung SM-M205F Verbose 4185 SamsungAlarmManager Sending to uid : 10244 action=null alarm=Alarm{a757a70 type 0 when 1595410200000 com.MyApp.andr.connect}
07-22 15:00:06.613 Samsung SM-M205F Warning 4185 BroadcastQueue Unable to launch app com.MyApp.andr.connect/10244 for broadcast Intent { flg=0x14 cmp=com.MyApp.andr.connect/crc640e87e93c5dbd1629.AlarmReceiver (has extras) }: process is bad
Update (Issue is still with Android 10)
I have managed to get notification when app is running, but now notifications are not firing when app is closed.
Note : I have made a sample which is working just fine with Android 10, Notifications are triggering when app is running or closed in the sample. I don't have to use foreground service. (I have used same approach in my work project). So There is something wrong in my work project which I am not able to figure out.
Here is the Android device logs when app is closed and notifications are about to trigger,
If someone can help !

All of the code will be frozen when your application is closed including the alarm manager.
If you want it still alive, we could use foreground service:
https://learn.microsoft.com/en-us/xamarin/android/app-fundamentals/services/foreground-services
This may be a little out of date, here is my approach to create the notification:
[Service]
public class SampleService : Service
{
public static string CHANNEL_ID = "com.channelid";
public static string CHANNEL_NAME = "com.channelname";
public override void OnCreate()
{
base.OnCreate();
}
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
registerNotificationChannel();
int notifyId = (int)JavaSystem.CurrentTimeMillis();
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
mBuilder.SetSmallIcon(Resource.Mipmap.ic_launcher);
if (Build.VERSION.SdkInt < BuildVersionCodes.N)
{
mBuilder.SetContentTitle("app name");
}
StartForeground(notifyId, mBuilder.Build());
return StartCommandResult.Sticky;
}
private void registerNotificationChannel()
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
NotificationManager mNotificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
NotificationChannel notificationChannel = mNotificationManager.GetNotificationChannel(CHANNEL_ID);
if (notificationChannel == null)
{
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME, NotificationImportance.High);
channel.EnableLights(true);
channel.LightColor = Color.Red;
channel.LockscreenVisibility = NotificationVisibility.Public;
mNotificationManager.CreateNotificationChannel(channel);
}
}
}
public override IBinder OnBind(Intent intent)
{
throw new NotImplementedException();
}
}
Update
Now you don't need to use alarmmanager any more .
In MainActivity
public static MainActivity Instance { get; set; }
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Instance = this;
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
In AlarmAndNotificationService
void IAlarmAndNotificationService.ScheduleLocalNotification(string notificationTitle, string notificationMessage, DateTime specificDateTime, TimeSpan timeSpan, int notificationId, NotificationInterval interval)
{
// start service here
Intent s = new Intent(MainActivity.Instance, typeof(SampleService));
MainActivity.Instance.StartService(s);
}
in SampleService
public class SampleService : Service
{
private Handler handler;
private Action runnable;
private bool isStarted;
private int DELAY_BETWEEN_LOG_MESSAGES = 5000; // set time span
private int NOTIFICATION_SERVICE_ID = 1001;
private int NOTIFICATION_AlARM_ID = 1002;
private string NOTIFICATION_CHANNEL_ID = "1003";
private string NOTIFICATION_CHANNEL_NAME = "MyChannel";
public override void OnCreate()
{
base.OnCreate();
handler = new Handler();
//here is what you want to do always, i just want to push a notification every 5 seconds here
runnable = new Action(() =>
{
if (isStarted)
{
DispatchNotificationThatAlarmIsGenerated("I'm running");
handler.PostDelayed(runnable, DELAY_BETWEEN_LOG_MESSAGES);
}
});
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
if (isStarted)
{
// service is already started
}
else
{
CreateNotificationChannel();
DispatchNotificationThatServiceIsRunning();
handler.PostDelayed(runnable, DELAY_BETWEEN_LOG_MESSAGES);
isStarted = true;
}
return StartCommandResult.Sticky;
}
public override void OnTaskRemoved(Intent rootIntent)
{
//base.OnTaskRemoved(rootIntent);
}
public override IBinder OnBind(Intent intent)
{
// Return null because this is a pure started service. A hybrid service would return a binder that would
// allow access to the GetFormattedStamp() method.
return null;
}
public override void OnDestroy()
{
// Stop the handler.
handler.RemoveCallbacks(runnable);
// Remove the notification from the status bar.
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.Cancel(NOTIFICATION_SERVICE_ID);
isStarted = false;
base.OnDestroy();
}
private void CreateNotificationChannel()
{
//Notification Channel
NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationImportance.Max);
notificationChannel.EnableLights(true);
notificationChannel.EnableVibration(true);
notificationChannel.SetVibrationPattern(new long[] { 100, 200, 300, 400, 500, 400, 300, 200, 400 });
NotificationManager notificationManager = (NotificationManager)this.GetSystemService(Context.NotificationService);
notificationManager.CreateNotificationChannel(notificationChannel);
}
//start a foreground notification to keep alive
private void DispatchNotificationThatServiceIsRunning()
{
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.SetDefaults((int)NotificationDefaults.All)
.SetSmallIcon(Resource.Drawable.icon)
.SetVibrate(new long[] { 100, 200, 300, 400, 500, 400, 300, 200, 400 })
.SetSound(null)
.SetChannelId(NOTIFICATION_CHANNEL_ID)
.SetPriority(NotificationCompat.PriorityDefault)
.SetAutoCancel(false)
.SetContentTitle("Mobile")
.SetContentText("My service started")
.SetOngoing(true);
NotificationManagerCompat notificationManager = NotificationManagerCompat.From(this);
StartForeground(NOTIFICATION_SERVICE_ID, builder.Build());
}
//every 5 seconds push a notificaition
private void DispatchNotificationThatAlarmIsGenerated(string message)
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
Notification.Builder notificationBuilder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
.SetSmallIcon(Resource.Drawable.icon)
.SetContentTitle("Alarm")
.SetContentText(message)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.Notify(NOTIFICATION_AlARM_ID, notificationBuilder.Build());
}
}

Related

Set DarkMode on the first page status bar xamarin forms android

Using xamarin forms and we are adding ability to switch between Dark-Light mode. All is good however the first page of the app in android whatever I do the status bar color wont change.
I guess that in the android project I have to call SetTheme(...) before OnCreate.
Or Am I missing something here?
Question
How do you set the status bar color depending on theme? code below does not change once the android has loaded
public void SetStatusBarColor(System.Drawing.Color color, bool darkStatusBarTint)
{
var activity = Platform.CurrentActivity;
var window = activity.Window;
window?.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
window?.ClearFlags(WindowManagerFlags.TranslucentStatus);
window?.SetStatusBarColor(color.ToPlatformColor());
var flag = (StatusBarVisibility)SystemUiFlags.LightStatusBar;
if (window != null)
{
window.DecorView.SystemUiVisibility = darkStatusBarTint ? flag : 0;
}
}
Suggestions?
thanks
Try this:
private void SetStatusBarColor(System.Drawing.Color color, bool darkStatusBarTint)
{
var activity = Platform.CurrentActivity;
var window = activity.Window;
if (window != null)
{
window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
window.ClearFlags(WindowManagerFlags.TranslucentStatus);
window.SetStatusBarColor(color.ToPlatformColor());
StatusBarVisibility flags = default;
if (darkStatusBarTint)
flags |= (StatusBarVisibility)SystemUiFlags.LightStatusBar;
else
flags &= ~(StatusBarVisibility)SystemUiFlags.LightStatusBar;
window.DecorView.SystemUiVisibility = flags;
}
}
Or
private void SetStatusBarColor(System.Drawing.Color color, bool darkStatusBarTint)
{
var activity = Platform.CurrentActivity;
var window = activity.Window;
if (window != null)
{
window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
window.ClearFlags(WindowManagerFlags.TranslucentStatus);
window.SetStatusBarColor(color.ToPlatformColor());
window.DecorView.SystemUiVisibility = darkStatusBarTint
? (StatusBarVisibility)SystemUiFlags.LightStatusBar
: StatusBarVisibility.Visible;
}
}
Both functions work.
You can update the StatusBar color in the MainActivity.OnCreate method and also listen to the App.Current.RequestedThemeChanged event.
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
ApplyStatusBarColor(App.Current.RequestedTheme);
App.Current.RequestedThemeChanged += (s, e) => ApplyStatusBarColor(e.RequestedTheme);
}
private void ApplyStatusBarColor(Xamarin.Forms.OSAppTheme osAppTheme)
{
if (osAppTheme == Xamarin.Forms.OSAppTheme.Dark)
SetStatusBarColor(Xamarin.Forms.Color.Blue, false);
else
SetStatusBarColor(Xamarin.Forms.Color.Yellow, true);
}
}

Run a background process to change backgroundimage of another page using timer in Xamarin.forms

Hi StackOverflow Team,
I am trying to run a background process in my App. This background process should update just Background image on one of the pages in the App every 15 seconds. So far, I tried to create a timer in the App OnStart() method and update the backgroundimage of the page in the BeginInvokeOnMainThread() method but with no success. Can anyone help me with this?
My Code -
{
private static Stopwatch stopWatch = new Stopwatch();
private const int defaultTimespan = 20;
private readonly HomePage homePage;
public App()
{
InitializeComponent();
try
{
MainPage = new MainPage();
homePage = new HomePage();
}
catch(Exception ex)
{
string str = ex.Message;
}
}
protected override void OnStart()
{
if (!stopWatch.IsRunning)
{
stopWatch.Start();
}
Device.StartTimer(new TimeSpan(0, 0, 10), () =>
{
// Logic for logging out if the device is inactive for a period of time.
if (stopWatch.IsRunning && stopWatch.Elapsed.Seconds >= defaultTimespan)
{
//prepare to perform your data pull here as we have hit the 1 minute mark
// Perform your long running operations here.
Device.BeginInvokeOnMainThread(() =>
{
// If you need to do anything with your UI, you need to wrap it in this.
// homePage.BackgroundImageSource = "goldengate.jpg";
homePage.ChangeBackgroundImage();
});
stopWatch.Restart();
}
// Always return true as to keep our device timer running.
return true;
});
}
protected override void OnSleep()
{
//stopWatch.Reset();
}
protected override void OnResume()
{
//stopWatch.Start();
}
//void ChangeHomePageImage()
//{
// Navigation.PushAsync(new HomePage(appBackground));
// Navigation.RemovePage(this);
//}
}
MainPage -
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:Excercise.Views"
x:Class="Excercise.MainPage" IsPresented="False">
<MasterDetailPage.Master>
<local:MenuPage x:Name="menuPage"></local:MenuPage>
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage>
<x:Arguments>
<local:HomePage x:Name="homePage"></local:HomePage>
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Detail>
</MasterDetailPage>
HomePage -
public partial class HomePage : ContentPage
{
private SQLiteAsyncConnection _connection;
public HomePage()
{
InitializeComponent();
// BindingContext = new HomePageViewModel();
_connection = DependencyService.Get<ISQLiteDb>().GetConnection();
loadData("");
}
public HomePage(string BackgroundimgPath)
{
InitializeComponent();
// BindingContext = new HomePageViewModel();
_connection = DependencyService.Get<ISQLiteDb>().GetConnection();
loadData(BackgroundimgPath);
}
public HomePage(string City, string LocationKey, string StateID)
{
InitializeComponent();
_connection = DependencyService.Get<ISQLiteDb>().GetConnection();
// BindingContext = new HomePageViewModel();
try
{
// Method Calls
}
catch (Exception)
{
DisplayAlert("Error", "There was an error loading this page.", "OK");
}
}
protected override void OnAppearing()
{
this.Title = App.AppTitle;
this.firstStacklayout.Margin = new Thickness(0, (Application.Current.MainPage.Height * 0.25), 0, 0);
base.OnAppearing();
}
you are creating an instance of HomePage and trying to update it, but it is NOT the same instance that is being displayed in your MasterDetail
try something like this
var md = (MasterDetailPage)MainPage;
var nav = (NavigationPage)md.DetailPage;
var home = (HomePage)nav.CurrentPage;
home.ChangeBackgroundImage();
alternately, you could use MessagingCenter to send a message to HomePage requesting that it udpate

Xamarin Forms Lifecycle

As App.cs is provided from Xamarin Forms I do bootstrap some of the stuff like AutoMapper there. However sometimes when the app is long in the background then the App's constructor fires again when going back from background.
This makes a bit exception since initial stuff being done twice or more.
Is that normal behaviour of XamarinForms lifecycle?
public App()
{
//Its called multiple times since app is running and going back from the background
InitializeComponent();
Bootstrap(new AppContainer(),
new TranslationBootstrap(),
new MapperBootstrap(), );
Navigate();
}
EDIT:
This happens on Android
MainActivity.cs
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
Rg.Plugins.Popup.Popup.Init(this, bundle);
CrossCurrentActivity.Current.Init(this, bundle);
RequestedOrientation = ScreenOrientation.Portrait;
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;
global::Xamarin.Forms.Forms.Init(this, bundle);
UserDialogs.Init(() => this);
InteractiveAlerts.Init(() => this);
LoadApplication(new App());
}
private static async void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs unobservedTaskExceptionEventArgs)
{
var newExc = new Exception("TaskSchedulerOnUnobservedTaskException", unobservedTaskExceptionEventArgs.Exception);
await LogUnhandledException(sender, newExc);
}
private static async void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
{
var newExc = new Exception("CurrentDomainOnUnhandledException", unhandledExceptionEventArgs.ExceptionObject as Exception);
await LogUnhandledException(sender, newExc);
}
internal static async Task LogUnhandledException(object sender, Exception exception)
{
var logger = DependencyService.Get<ILogger>();
await logger.LogAsync(sender, exception.Source, exception.Message, LogType.Fatal);
}
protected override void OnResume()
{
base.OnResume();
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != Permission.Granted)
{
ActivityCompat.RequestPermissions(this,
new[] { Manifest.Permission.WriteExternalStorage, Manifest.Permission.ReadExternalStorage }, 101);
}
}

Navigating to specific page on push notification click

I am working with xamarin forms. In Xamarin Android I received the notification when app not in foregroud/backgroud (i.e killed the app). When clicking on the notification I need to navigate to specific page.
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
string notificationMessage = intent.GetStringExtra(Constants.MESSAGE);
string notificationThreadId = intent.GetStringExtra(Constants.MESSAGE_THREAD_ID);
bool isFromNotificaion = true;
PushNotificationLog notificationLog = new PushNotificationLog(notificationMessage, notificationThreadId, isFromNotificaion);
ConferenceMobileApp.App app = new App(notificationLog);
LoadApplication(app);
}
The OnNewIntent is called when app in foreground or background , not when app is killed.
And my notification send code is below
void SendNotification(RemoteMessage message)
{
string messageBody = "";
string messageThreadId = "";
message.Data.TryGetValue(MESSAGE,out messageBody);
message.Data.TryGetValue(MESSAGE_THREAD_ID, out messageThreadId);
string messageLogId = "0";//message.Data.TryGetValue(MESSAGE_LOG_ID);
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
intent.AddFlags(ActivityFlags.SingleTop);
intent.PutExtra(MESSAGE, messageBody);
intent.PutExtra(MESSAGE_THREAD_ID, messageThreadId);
//intent.PutExtra(MESSAGE_LOG_ID, messageLogId);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.OneShot);
var notificationBuilder = new Notification.Builder(this).SetSmallIcon(Resource.Drawable.common_google_signin_btn_icon_dark)
.SetContentTitle("PKConf")
.SetContentText(messageBody)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = NotificationManager.FromContext(this);
//setting notification id
int notificaionId = Convert.ToInt32(messageLogId);
notificationManager.Notify(notificaionId, notificationBuilder.Build());
}
How could I achieve this when app is not in foreground/background?
Please try with below events in your mainactivity.cs
protected override void OnResume()
{
base.OnResume(); // Always call the superclass first.
}
protected override void OnPause()
{
base.OnPause(); // Always call the superclass first
}
You can try overriding OnResume
protected async override void OnResume()
{
base.OnResume();
// Your Code. I would guess same code as you have in OnNewIntent
}

Banner Advertisement with Xamarin.Forms

I just want to know about the banner advertisements supported with Xamarin.Forms without any patch or loophole. Is there any advertisement provider who are providing their SDKs with the Xamarin.Forms?
Thanks in advance.
There are both SDK and step-by-step examples for Google AdMob for Xamarin.Android. You are going to need the Xamarin.GooglePlaySerives.Ads nuget.
I use it to show ads in my Xamarin.Forms app published at Google Play.
Here is the sample code for the android part of your application:
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Support.V7.App;
using Android.Gms.Ads;
using Android;
namespace AdMobExample
{
[Activity (Label = "#string/app_name", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
protected AdView mAdView;
protected InterstitialAd mInterstitialAd;
protected Button mLoadInterstitialButton;
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
SetContentView (Resource.Layout.activity_main);
mAdView = FindViewById<AdView> (Resource.Id.adView);
var adRequest = new AdRequest.Builder ().Build ();
mAdView.LoadAd (adRequest);
mInterstitialAd = new InterstitialAd (this);
mInterstitialAd.AdUnitId = GetString (Resource.String.test_interstitial_ad_unit_id);
mInterstitialAd.AdListener = new AdListener (this);
mLoadInterstitialButton = FindViewById<Button> (Resource.Id.load_interstitial_button);
mLoadInterstitialButton.SetOnClickListener (new OnClickListener (this));
}
protected void RequestNewInterstitial ()
{
var adRequest = new AdRequest.Builder ().Build ();
mInterstitialAd.LoadAd (adRequest);
}
protected void BeginSecondActivity ()
{
var intent = new Intent (this, typeof(SecondActivity));
StartActivity (intent);
}
protected override void OnPause ()
{
if (mAdView != null) {
mAdView.Pause ();
}
base.OnPause ();
}
protected override void OnResume ()
{
base.OnResume ();
if (mAdView != null) {
mAdView.Resume ();
}
if (!mInterstitialAd.IsLoaded) {
RequestNewInterstitial ();
}
}
protected override void OnDestroy ()
{
if (mAdView != null) {
mAdView.Destroy ();
}
base.OnDestroy ();
}
class AdListener : Android.Gms.Ads.AdListener
{
MainActivity that;
public AdListener (MainActivity t)
{
that = t;
}
public override void OnAdClosed ()
{
that.RequestNewInterstitial ();
that.BeginSecondActivity ();
}
}
class OnClickListener : Java.Lang.Object, View.IOnClickListener
{
MainActivity that;
public OnClickListener (MainActivity t)
{
that = t;
}
public void OnClick (View v)
{
if (that.mInterstitialAd.IsLoaded) {
that.mInterstitialAd.Show ();
} else {
that.BeginSecondActivity ();
}
}
}
}
}
There is also a ste-by-step guide for AdMob ads for Xamarin.iOS:
using Google.MobileAds;
...
const string intersitialId = "<Get your ID at google.com/ads/admob>";
Interstitial adInterstitial;
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
CreateAndRequestInterstitial ();
}
public void AfterSomeTime ()
{
if (adInterstitial.IsReady)
adInterstitial.PresentFromRootViewController (navController);
}
void CreateAndRequestInterstitial ()
{
adInterstitial = new Interstitial (intersitialId);
adInterstitial.ScreenDismissed += (sender, e) => {
// Interstitial is a one time use object. That means once an interstitial is shown, HasBeenUsed
// returns true and the interstitial can't be used to load another ad.
// To request another interstitial, you'll need to create a new Interstitial object.
adInterstitial.Dispose ();
adInterstitial = null;
CreateAndRequestInterstitial ();
};
var request = Request.GetDefaultRequest ();
// Requests test ads on devices you specify. Your test device ID is printed to the console when
// an ad request is made. GADBannerView automatically returns test ads when running on a
// simulator. After you get your device ID, add it here
request.TestDevices = new [] { Request.SimulatorId.ToString () };
adInterstitial.LoadRequest (request);
}

Resources