Xamarin.Android Run a Method/Task Even My App is Close - xamarin.android

I'm a newbie for xamarin.Android can anybudy guide me for doing the " Run a Method/Task Even My App is Close " in Xamarin.Android for Notification purpose

You could use Foreground Sevice by start a foregroud notification
and you could refer to the similar case,refer to the codes
1.Create a Service MyService.cs :
[Service(Enabled = true)]
public class MyService : Service
{
private Handler handler;
private Action runnable;
private bool isStarted;
private int DELAY_BETWEEN_LOG_MESSAGES = 5000;
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());
}
}
2.in your activity :
protected override void OnResume()
{
base.OnResume();
StartMyRequestService();
}
public void StartMyRequestService()
{
var serviceToStart = new Intent(this, typeof(MyService));
StartService(serviceToStart);
}
update:
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);
}
});
}

Related

Multi Column Picker for Xamarin iOS

I am building a multi-column picker control, and I have some issues with the xamarin.ios renderer.
I have two columns in the picker. One for the month and another for the year. I have got the values binded to the picker control properly. However when I make a selection the app crashes.
The error is Specified cast is not valid.
Stacktrace is
at Xamarin.Forms.Platform.iOS.PickerRendererBase`1[TControl].OnEnded
(System.Object sender, System.EventArgs eventArgs) [0x0000b] in
<0648e2dffe9e4201b8c6e274ced6579f>:0 at
UIKit.UIControlEventProxy.Activated () [0x00004] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.16.0.5/src/Xamarin.iOS/UIKit/UIControl.cs:38
My ios Custom Renderer code is as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using CoreGraphics;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(YearMonthPicker), typeof(YearMonthPickerRenderer))]
namespace TestApp.iOS.Renderers
{
public class YearMonthPickerRenderer : PickerRenderer
{
private MonthYear _monthYear = new MonthYear();
private UILabel _monthLabel;
private UILabel _yearLabel;
public YearMonthPickerRenderer()
{
_monthYear.Months = new List<string>();
_monthYear.Years = new List<int>();
InitValues();
}
private void InitValues()
{
_monthYear.Months = new List<string>{
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"Sepetember",
"October",
"November",
"December"
};
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var customPicker = e.NewElement as YearMonthPicker;
var startYear = customPicker.StartYear;
var endYear = customPicker.EndYear;
do
{
_monthYear.Years.Add(startYear);
startYear++;
} while (startYear <= endYear);
if (_monthLabel == null)
{
_monthLabel = new UILabel();
_monthLabel.Text = customPicker.MonthLabel;
_monthLabel.TextColor = customPicker.LabelColor.ToUIColor();
_monthLabel.Font = _monthLabel.Font.WithSize(14.0f);
}
if (_yearLabel == null)
{
_yearLabel = new UILabel();
_yearLabel.Text = customPicker.YearLabel;
_yearLabel.TextColor = customPicker.LabelColor.ToUIColor();
_yearLabel.Font = _yearLabel.Font.WithSize(14.0f);
}
if (Control is UITextField textField)
{
var pickerView = textField.InputView as UIPickerView;
var yearMonthModel = new YearMonthModel(textField, _monthYear);
var yearMonthDelegate = new YearMonthPickerDelegate(textField, _monthYear);
pickerView.Model = yearMonthModel;
pickerView.Delegate = yearMonthDelegate;
textField.BorderStyle = UITextBorderStyle.None;
textField.AddSubview(_monthLabel);
textField.AddSubview(_yearLabel);
}
}
var toolbar = new UIToolbar(new CGRect(0.0f, 0.0f, Control.Frame.Size.Width, 44.0f));
toolbar.Items = new[]
{
new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace),
new UIBarButtonItem("Done",
UIBarButtonItemStyle.Done,
delegate {
Control.ResignFirstResponder();
})
};
if (this.Control != null)
{
Control.InputAccessoryView = toolbar;
}
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
_monthLabel.Frame = new CGRect(0, -10,
Control.Frame.Width / 2,
Control.Frame.Height - 5);
_yearLabel.Frame = new CGRect(Control.Frame.Width / 2, -10,
Control.Frame.Width / 2,
Control.Frame.Height - 5);
}
protected override void Dispose(bool disposing)
{
_monthLabel?.Dispose();
_yearLabel?.Dispose();
_monthLabel = null;
_yearLabel = null;
base.Dispose(disposing);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
}
public class YearMonthModel : UIPickerViewModel
{
private UITextField _uITextField;
private string _selectedMonth = string.Empty;
private string _selectedYear = string.Empty;
private MonthYear _monthYear;
public YearMonthModel(UITextField uITextField, MonthYear monthYear)
{
_uITextField = uITextField;
_monthYear = monthYear;
}
public override nint GetComponentCount(UIPickerView pickerView)
{
return 2;
}
public override nint GetRowsInComponent(UIPickerView pickerView, nint component)
{
if (component == 0)
return _monthYear.Months.Count;
else
return _monthYear.Years.Count;
}
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
return _monthYear.Months[(int)row];
else
return _monthYear.Years[(int)row].ToString();
}
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
_selectedMonth = _monthYear.Months[(int)row];
if (component == 1)
_selectedYear = _monthYear.Years[(int)row].ToString();
_uITextField.Text = $"{_selectedMonth} {_selectedYear}";
}
public override nfloat GetComponentWidth(UIPickerView picker, nint component)
{
if (component == 0)
return 140f;
else
return 100f;
}
public override nfloat GetRowHeight(UIPickerView picker, nint component)
{
return 40f;
}
}
public class YearMonthPickerDelegate : UIPickerViewDelegate
{
private UITextField _uITextField;
private string _selectedMonth = string.Empty;
private string _selectedYear = string.Empty;
private MonthYear _monthYear;
public YearMonthPickerDelegate(UITextField uITextField, MonthYear monthYear)
{
_uITextField = uITextField;
_monthYear = monthYear;
}
public override string GetTitle(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
return _monthYear.Months[(int)row];
else
return _monthYear.Years[(int)row].ToString();
}
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
if (component == 0)
_selectedMonth = _monthYear.Months[(int)row];
if (component == 1)
_selectedYear = _monthYear.Years[(int)row].ToString();
_uITextField.Text = $"{_selectedMonth} {_selectedYear}";
}
}
public class MonthYear
{
public List<string> Months { get; set; }
public List<int> Years { get; set; }
}
}
Edit 1
I am adding the YearMonthPicker class
public class YearMonthPicker : Picker
{
public YearMonthPicker()
{
}
public static readonly BindableProperty StartYearProperty =
BindableProperty.Create(nameof(StartYear), typeof(int), typeof(YearMonthPicker), 1900);
public int StartYear
{
get { return (int)GetValue(StartYearProperty); }
set { SetValue(StartYearProperty, value); }
}
public static readonly BindableProperty EndYearProperty =
BindableProperty.Create(nameof(EndYear), typeof(int), typeof(YearMonthPicker), 9999);
public int EndYear
{
get { return (int)GetValue(EndYearProperty); }
set { SetValue(EndYearProperty, value); }
}
public static readonly BindableProperty YearLabelProperty =
BindableProperty.Create(nameof(YearLabel), typeof(string), typeof(YearMonthPicker), string.Empty);
public string YearLabel
{
get { return (string)GetValue(YearLabelProperty); }
set { SetValue(YearLabelProperty, value); }
}
public static readonly BindableProperty MonthLabelProperty =
BindableProperty.Create(nameof(MonthLabel), typeof(string), typeof(YearMonthPicker), string.Empty);
public string MonthLabel
{
get { return (string)GetValue(MonthLabelProperty); }
set { SetValue(MonthLabelProperty, value); }
}
public static readonly BindableProperty LabelColorProperty =
BindableProperty.Create(nameof(LabelColor), typeof(Color), typeof(YearMonthPicker), default(Color));
public Color LabelColor
{
get { return (Color)GetValue(LabelColorProperty); }
set { SetValue(LabelColorProperty, value); }
}
}
I appreciate if someone can help me identify what causes the crash.
Finally, I found your problem is you did not assign the pickerView to the textField's inputView.
To assign the picker to the textField's inputView, use below code:
if (Control is UITextField textField)
{
//Change here
var pickerView = new UIPickerView();
Control.InputView = pickerView;
var yearMonthModel = new YearMonthModel(textField, _monthYear);
var yearMonthDelegate = new YearMonthPickerDelegate(textField, _monthYear);
pickerView.Model = yearMonthModel;
pickerView.Delegate = yearMonthDelegate;
textField.BorderStyle = UITextBorderStyle.None;
textField.AddSubview(_monthLabel);
textField.AddSubview(_yearLabel);
}

Bindable Property is not updating view on iOS

I am creating a cross platform app on xamarin.forms. I needed a custom renderer with gradient background and i've created it and it works well on both android and ios.
However, when i want to change the colors of the gradient background, it does not work on iOS.
Test.xaml.cs
namespace KiaiDay
{
public class TesteViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Color _startColor = Color.Green;
public Color SStartColor
{
get => _startColor;
set { _startColor = value; OnPropertyChanged(nameof(SStartColor)); }
}
private Color _endColor = Color.Blue;
public Color EEndColor
{
get => _endColor;
set { _endColor = value; OnPropertyChanged(nameof(EEndColor)); }
}
public ICommand Select
{
get => new Command(() =>
{
SStartColor = Color.Red;
EEndColor = Color.Brown;
});
}
#region INotifyPropertyChanged Implementation
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged == null)
return;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class teste : ContentPage
{
public teste()
{
BindingContext = new TesteViewModel();
InitializeComponent();
}
}
}
Test.xaml
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<local:ShadowFrame StartColor="{Binding SStartColor}" EndColor="{Binding EEndColor}" HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="Teste"/>
<local:ShadowFrame.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Command="{Binding Select}"/>
</local:ShadowFrame.GestureRecognizers>
</local:ShadowFrame>
</StackLayout>
</ContentPage.Content>
Custom Renderer in PCL
public class ShadowFrame : Frame
{
public static BindableProperty ElevationProperty = BindableProperty.Create(nameof(Elevation), typeof(float), typeof(ShadowFrame), default(float));
public static readonly BindableProperty StartColorProperty = BindableProperty.Create(nameof(StartColor), typeof(Color), typeof(ShadowFrame), default(Color));
public static readonly BindableProperty EndColorProperty = BindableProperty.Create(nameof(EndColor), typeof(Color), typeof(ShadowFrame), default(Color));
public float Elevation
{
get { return (float)GetValue(ElevationProperty); }
set { SetValue(ElevationProperty, value); }
}
public Color StartColor
{
get { return (Color)GetValue(StartColorProperty); }
set { SetValue(StartColorProperty, value); }
}
public Color EndColor
{
get { return (Color)GetValue(EndColorProperty); }
set { SetValue(EndColorProperty, value); }
}
}
iOS Renderer(i think the problem is here)
[assembly: ExportRenderer(typeof(ShadowFrame), typeof(ShadowFrameRenderer))]
namespace KiaiDay.iOS.Renderers
{
public class ShadowFrameRenderer : FrameRenderer
{
CAGradientLayer gradientLayer;
private Color StartColor { get; set; }
private Color EndColor { get; set; }
CGRect rect;
public static void Initialize()
{
// empty, but used for beating the linker
}
public ShadowFrameRenderer() => gradientLayer = new CAGradientLayer();
public override void Draw(CGRect rect)
{
this.rect = rect;
var stack = (ShadowFrame)this.Element;
StartColor = stack.StartColor;
EndColor = stack.EndColor;
CGColor startColor = this.StartColor.ToCGColor();
CGColor endColor = this.EndColor.ToCGColor();
#region for Vertical Gradient
//var gradientLayer = new CAGradientLayer();
#endregion
#region for Horizontal Gradient
//var gradientLayer = new CAGradientLayer()
//{
// StartPoint = new CGPoint(0, 0.5),
// EndPoint = new CGPoint(1, 0.5)
//};
#endregion
gradientLayer.Frame = rect;
gradientLayer.Colors = new CGColor[] {
startColor,
endColor
};
NativeView.Layer.InsertSublayer(gradientLayer, 0);
base.Draw(rect);
}
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
var stack = (ShadowFrame)e.NewElement;
if (e.OldElement != null || Element == null)
{
return;
}
try
{
this.StartColor = stack.StartColor;
this.EndColor = stack.EndColor;
UpdateShadow();
Draw(rect);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("ERROR:", ex.Message);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var stack = (ShadowFrame) sender;
if (e.PropertyName == ShadowFrame.ElevationProperty.PropertyName)
{
UpdateShadow();
}
if (e.PropertyName == ShadowFrame.StartColorProperty.PropertyName)
{
this.StartColor = stack.StartColor;
}
if (e.PropertyName == ShadowFrame.EndColorProperty.PropertyName)
{
this.EndColor = stack.EndColor;
}
}
private void UpdateShadow()
{
var shadowFrame = (ShadowFrame)Element;
// Update shadow to match better material design standards of elevation
Layer.ShadowRadius = shadowFrame.Elevation;
Layer.ShadowColor = UIColor.Gray.CGColor;
Layer.ShadowOffset = new CGSize(2, 2);
Layer.ShadowOpacity = 0.80f;
Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
Layer.MasksToBounds = false;
}
}
}
I would like to update iOS view immediatly after i change the color through the command.
If you change a property and the view doesn't update the issue has to be in OnElementPropertyChanged.
You need to call UpdateShadow() and Draw().
After digging a lot i think i have found a solution:
iOS Custom Renderer :
public class GradientStackColorRenderer : VisualElementRenderer<Frame>
{
private Color StartColor { get; set; }
private Color EndColor { get; set; }
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
if (value.Width > 0 && value.Height > 0)
{
foreach (var layer in NativeView?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
layer.Frame = new CGRect(0, 0, value.Width, value.Height);
}
base.Frame = value;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<StackLayout> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
try
{
var stack = e.NewElement as GradientColorStack;
this.StartColor = stack.StartColor;
this.EndColor = stack.EndColor;
AdicionarGradiente();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(#"ERROR:", ex.Message);
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var stack = this.Element as GradientColorStack;
if(e.PropertyName == GradientColorStack.StartColorProperty.PropertyName)
{
this.StartColor = stack.StartColor;
AdicionarGradiente();
}
if (e.PropertyName == GradientColorStack.EndColorProperty.PropertyName)
{
this.EndColor = stack.EndColor;
AdicionarGradiente();
}
}
public void AdicionarGradiente()
{
var gradient = new CAGradientLayer();
gradient.CornerRadius = NativeView.Layer.CornerRadius = 5;
gradient.Colors = new CGColor[] { StartColor.ToCGColor(), EndColor.ToCGColor() };
var layer = NativeView?.Layer.Sublayers.LastOrDefault();
NativeView?.Layer.InsertSublayerBelow(gradient, layer);
}
public static CGColor ToCGColor(Color color)
{
return new CGColor(CGColorSpace.CreateSrgb(), new nfloat[] { (float)color.R, (float)color.G, (float)color.B, (float)color.A });
}
}

exception `[NSURL isAdtechEvent]: unrecognized selector sent to instance` when removing HockeySDK

I want to replace HockeyApp SDK with App Center SDK. But when I remove the following line of code var manager = BITHockeyManager.SharedHockeyManager; the following unhandled exception occurs on startup: [NSURL isAdtechEvent]: unrecognized selector sent to instance. I have no clue how Adtech is related to HockeySDK. And there is no method or event isAdtechEvent or similar in my code. What can I do to narrow down this error?
Here you'll find the AppDelegate class:
using System;
using System.Threading;
using MvvmCross.Platform;
using MvvmCross.iOS.Platform;
using MvvmCross.iOS.Views.Presenters;
using MvvmCross.Core.ViewModels;
using Foundation;
using GoogleConversionTracking.Unified;
using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using MTiRate;
using PCLStorage;
using PushNotification.Plugin;
using UIKit;
namespace MyApp
{
public static class ShortcutIdentifier
{
public const string Parkspace = "parkingspace";
}
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public class AppDelegate : MvxApplicationDelegate
{
private const string ConversionId = "1054453082";
private const string ConversionLabel = "VaLvCNaO018Q2trm9gM";
private const string ConversionValue = "0.00";
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
private static Action _afterPushRegistrationAction;
private UIWindow _window;
// Must override the Window property for iRate to work properly
public override UIWindow Window
{
get => _window;
set => _window = value;
}
static AppDelegate()
{
// rating window configuration
iRate.SharedInstance.DaysUntilPrompt = 0.5f; // default is 10!
iRate.SharedInstance.UsesUntilPrompt = 10;
iRate.SharedInstance.RemindPeriod = 30; // 30 days
iRate.SharedInstance.PromptForNewVersionIfUserRated = false;
iRate.SharedInstance.PromptAtLaunch = false; // trigger prompt manually for it doesn't show on splash screen
// texts
iRate.SharedInstance.MessageTitle = $"{AppResources.RatingMessageTitle} {iRate.SharedInstance.ApplicationName}";
iRate.SharedInstance.Message = AppResources.RatingMessage;
iRate.SharedInstance.RateButtonLabel = AppResources.RatingRateButton;
iRate.SharedInstance.RemindButtonLabel = AppResources.RatingRemindButton;
iRate.SharedInstance.CancelButtonLabel = AppResources.RatingCancelButton;
}
public UIApplicationShortcutItem LaunchedShortcutItem { get; set; }
private UIButton _btn;
public static MvxIosViewPresenter IosViewPresenter { get; set; }
public override bool FinishedLaunching(UIApplication app, NSDictionary launchOptions)
{
var shouldPerformAdditionalDelegateHandling = true;
// Get possible shortcut item
if (launchOptions != null)
{
LaunchedShortcutItem = launchOptions[UIApplication.LaunchOptionsShortcutItemKey] as UIApplicationShortcutItem;
shouldPerformAdditionalDelegateHandling = (LaunchedShortcutItem == null);
}
AppCenter.Start(Settings.Default.AppCenterSecretiOS, typeof(Analytics), typeof(Crashes));
_window = new UIWindow(UIScreen.MainScreen.Bounds);
if (_btn == null)
{
var viewController = new UIViewController();
_window.RootViewController = viewController;
var super = viewController.View;
_btn = new UIButton(UIButtonType.Custom)
{
AccessibilityIdentifier = "StartTrigger",
BackgroundColor = UIColor.Red,
TranslatesAutoresizingMaskIntoConstraints = false
};
_btn.SetTitle("StartTrigger", UIControlState.Normal);
super.AddSubview(_btn);
super.AddConstraint(NSLayoutConstraint.Create(_btn, NSLayoutAttribute.CenterX, NSLayoutRelation.Equal,
super, NSLayoutAttribute.CenterX, 1.0f, 1.0f));
super.AddConstraint(NSLayoutConstraint.Create(_btn, NSLayoutAttribute.CenterY, NSLayoutRelation.Equal,
super, NSLayoutAttribute.CenterY, 1.0f, 1.0f));
_btn.TouchDown += (object sender, EventArgs e) =>
{
StartMvvmCross();
_btn.RemoveFromSuperview();
_btn = null;
};
super.BringSubviewToFront(_btn);
}
StartMvvmCross();
_window.MakeKeyAndVisible();
_window.BackgroundColor = UIColor.White;
return shouldPerformAdditionalDelegateHandling;
}
private void StartMvvmCross()
{
CrossPushNotification.Initialize<AppleCrossPushNotificationListenerService>();
//Initialize Google Conversion Tracking with respective parameters
ACTReporter reporter = new ACTConversionReporter(ConversionId, ConversionLabel, ConversionValue, "USD", false);
reporter.Report();
IosViewPresenter = new MvxSlidingPanelsTouchViewPresenter(this, _window);
var setup = new Setup(this, IosViewPresenter);
setup.Initialize();
var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
_window.MakeKeyAndVisible();
_window.BackgroundColor = UIColor.White;
UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval(3600);
}
public static void AskForPushPermissionsAndRegister(Action continueWith = null)
{
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
if (!UIApplication.SharedApplication.IsRegisteredForRemoteNotifications ||
string.IsNullOrEmpty(CrossPushNotification.Current.Token))
{
CrossPushNotification.Current.Register();
_afterPushRegistrationAction = continueWith;
}
else
{
continueWith?.Invoke();
}
}
else
{
if (UIApplication.SharedApplication.EnabledRemoteNotificationTypes == UIRemoteNotificationType.None ||
string.IsNullOrEmpty(CrossPushNotification.Current.Token))
{
CrossPushNotification.Current.Register();
_afterPushRegistrationAction = continueWith;
}
else
{
continueWith?.Invoke();
}
}
}
public bool HandleShortcutItem(UIApplicationShortcutItem shortcutItem)
{
var handled = false;
if (shortcutItem == null) return false;
var routing = Mvx.Resolve<IRoutingService>();
switch (shortcutItem.Type)
{
case ShortcutIdentifier.Parkspace:
routing.Route("fzag://shortcut/parking?id=scan");
handled = true;
break;
}
return handled;
}
public override void PerformActionForShortcutItem(UIApplication application, UIApplicationShortcutItem shortcutItem,
UIOperationHandler completionHandler)
{
completionHandler(HandleShortcutItem(shortcutItem));
}
public override void OnActivated(UIApplication application)
{
// Handle any shortcut item being selected
HandleShortcutItem(LaunchedShortcutItem);
// Clear shortcut after it's been handled
LaunchedShortcutItem = null;
}
public override void ReceivedLocalNotification(UIApplication application, UILocalNotification notification)
{
// will be called if was clicked
if (notification.UserInfo == null || !notification.UserInfo.ContainsKey(FromObject("url")))
return;
var url = notification.UserInfo["url"].ToString();
var normalized = Uri.UnescapeDataString(url);
var routing = new RoutingService();
if (routing.CanRoute(normalized))
routing.Route(normalized);
}
public override bool OpenUrl(UIApplication app, NSUrl url, string srcApp, NSObject annotation)
{
var normalized = Uri.UnescapeDataString(url.ToString());
var routing = new RoutingService();
if (routing.CanRoute(normalized))
routing.Route(normalized);
return true;
}
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
var settings = Mvx.Resolve<IAppSettingsService>();
settings.PushNotifications = false;
if (CrossPushNotification.Current is IPushNotificationHandler handler)
handler.OnErrorReceived(error);
}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
var appSettings = Mvx.Resolve<IAppSettingsService>();
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
if (!application.IsRegisteredForRemoteNotifications)
{
appSettings.PushNotifications = false;
return;
}
}
else
{
if (application.EnabledRemoteNotificationTypes == UIRemoteNotificationType.None)
{
appSettings.PushNotifications = false;
return;
}
}
var handler = CrossPushNotification.Current as IPushNotificationHandler;
if (handler == null) return;
handler.OnRegisteredSuccess(deviceToken);
if (App.IsInitialized && Mvx.CanResolve<ILoginService>())
{
var loginService = Mvx.Resolve<ILoginService>();
try
{
AsyncHelper.RunSync(() => loginService.UpdateDeviceIdentificationAsync());
}
catch (Exception ex)
{
Log.Error(ex);
}
}
_afterPushRegistrationAction?.Invoke();
}
public override void DidRegisterUserNotificationSettings(UIApplication application,
UIUserNotificationSettings notificationSettings)
{
application.RegisterForRemoteNotifications();
}
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
Action<UIBackgroundFetchResult> completionHandler)
{
if (CrossPushNotification.Current is IPushNotificationHandler handler)
handler.OnMessageReceived(userInfo);
}
public override void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
{
if (CrossPushNotification.Current is IPushNotificationHandler handler)
handler.OnMessageReceived(userInfo);
}
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application,
UIWindow forWindow)
{
try
{
if (App.IsInitialized && Mvx.CanResolve<IMvxIosViewPresenter>())
{
if (Mvx.Resolve<IMvxIosViewPresenter>() is MvxIosViewPresenter mvxIosViewPresenter)
{
var viewController = mvxIosViewPresenter.MasterNavigationController.TopViewController;
return viewController.GetSupportedInterfaceOrientations();
}
}
}
catch
{
// can be called before Mvx is setup
}
return UIInterfaceOrientationMask.Portrait;
}
public override void PerformFetch(UIApplication application, Action<UIBackgroundFetchResult> completionHandler)
{
var accountStorage = new AppleAccountStorage();
if (!accountStorage.HasAccount)
{
completionHandler(UIBackgroundFetchResult.NoData);
return;
}
var voidMessenger = new VoidMessenger();
var restService = new RestService(AppleCultureService.Instance);
var authenticationService = new AuthenticationService(restService, voidMessenger, accountStorage);
var plannerService = new PlannerService(restService, authenticationService, FileSystem.Current,
new ZipService());
var tripService = new RealmTravelPlannerBookmarkService(new Lazy<IPlannerService>(() => plannerService), voidMessenger, accountStorage);
var command = new ProfileDataUpdateCommand(tripService, accountStorage, authenticationService,
plannerService);
try
{
AsyncHelper.RunSync(() => command.UpdateAsync(CancellationToken.None));
}
catch (Exception)
{
completionHandler (UIBackgroundFetchResult.Failed);
return;
}
completionHandler(UIBackgroundFetchResult.NewData);
}
}
}

Calling a page from a table

I am doing this: https://developer.xamarin.com/guides/ios/user_interface/controls/tables/creating-tables-in-a-storyboard/
but I can't get public override void PrepareForSegue(UIStoryboardSegue segue, Foundation.NSObject sender)
to get called when I click on a table.
Why?
Has anyone had this problem before?
I download their project and it works, but it does not in my project. Why? I am missing something but I am not sure what it is.
public partial class ViewController : UITableViewController
{
protected ViewController(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
CreateTableItems();
// Add(table);
// Perform any additional setup after loading the view, typically from a nib.
}
public override void PrepareForSegue(UIStoryboardSegue segue, Foundation.NSObject sender)
{
if (segue.Identifier == "TaskSegue")
{ // set in Storyboard
var navctlr = segue.DestinationViewController as wrestingControllor;
if (navctlr != null)
{
var source = table.Source as ScheduleTableViewSource;
var rowPath = table.IndexPathForSelectedRow;
var item = source.GetItem(rowPath.Row);
navctlr.SetTask(this, item); // to be defined on the TaskDetailViewController
}
}
// var page2ViewController = segue.DestinationViewController as Page2ViewController;
// page2ViewController.Name = "dan";
}
void errorMessage(string message)
{
UIAlertView alert = new UIAlertView()
{
Title = "Error",
Message = message,
};
alert.AddButton("OK");
alert.Show();
}
protected void CreateTableItems()
{
bool check = NetworkInterface.GetIsNetworkAvailable();
try
{
if (check)
{
Service1 client = new Service1();
var schools = client.School_lists();
table.Source = new ScheduleTableViewSource(schools, this);
}
else
{
errorMessage("Error no Internet connection");
return;
}
}
catch(Exception e)
{
errorMessage("Error no Internet connection");
return;
}
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}

ViewController is not getting released after being popped from navigation stack in Xamarin.iOS

I would like to assure people that I had gone through all the article over internet explaining memory leaks when using button to pop the controller from stack ,and I am aware of strong reference which UIButton creates while triggering events with Lambdas .
I had tried all that none seem to be working for me .
Problem statement
I have a UICollectionViewController as Root ViewController and a floating buttons on top of it which I have created programmatically and added as subviews.
These buttons push viewcontrollers in to stack .
here is the method where I push the controller .
private void HandleClick(object sender, EventArgs e) {
var button = sender as UIButton;
var board = UIStoryboard.FromName("Main", NSBundle.MainBundle);
switch (button.Tag) {
case 3: {
var vc = board.InstantiateViewController("BibleViewController");
this.NavigationController.PushViewController(vc, true);
vc = null;
break;
}
case 5: {
var vc = board.InstantiateViewController("RecordingViewController");
this.NavigationController.PushViewController(vc, true);
vc = null;
break;
}
case 7: {
var vc = board.InstantiateViewController("CameraFbController");
this.NavigationController.PushViewController(vc, true);
vc = null;
break;
}
case 6: {
var vc = board.InstantiateViewController("NewEmojiController");
this.NavigationController.PushViewController(vc, true);
vc = null;
break;
}
case 4: {
var vc = board.InstantiateViewController("WriteNShareController");
this.NavigationController.PushViewController(vc, true);
vc = null;
break;
}
default : {
break;
}
}
}
Assume I am pushing BibleViewController (Case 3:)
Please find the code for this controller
public partial class BibleHomeController : UIViewController
{
IList<string> items;
IList<string> item1;
public BibleHomeController() : base("BibleHomeController", null)
{
}
public BibleHomeController(IntPtr handle) : base (handle)
{
}
~BibleHomeController() {
Console.WriteLine("it was called ");
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
LoadJson();
tableView.DataSource = new BTableViewDataSource(items);
tableView.Delegate = new TableDelegate(items,this);
tableView.RegisterNibForCellReuse(UINib.FromName("BookCell",NSBundle.MainBundle),BookCell.Key);
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
backBtn.TouchUpInside += HandleBackClick;
nwBtn.TouchUpInside += newBtn;
oldBtn.TouchUpInside += oldBtnHanle;
}
private void HandleBackClick(object sender, EventArgs e)
{
this.NavigationController.PopViewController(true);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
backBtn.TouchUpInside -= HandleBackClick;
nwBtn.TouchUpInside -= newBtn;
oldBtn.TouchUpInside -= oldBtnHanle;
backBtn = null;
nwBtn = null;
oldBtn = null;
tableView = null;
}
private void newBtn(object sender, EventArgs e)
{
tableView.DataSource = new BTableViewDataSource(item1);
tableView.Delegate = new TableDelegate(item1,this);
tableView.ReloadData();
}
private void oldBtnHanle(object sender, EventArgs e)
{
tableView.DataSource = new BTableViewDataSource(items);
tableView.Delegate = new TableDelegate(items,this);
tableView.ReloadData();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
private void LoadJson() {
using (StreamReader r = new StreamReader("BibleSection/BibleBooks/Books.json")) {
string json = r.ReadToEnd();
items = JsonConvert.DeserializeObject<List<string>>(json);
}
using (StreamReader r = new StreamReader("BibleSection/BibleBooks/NewBook.json"))
{
string json = r.ReadToEnd();
item1 = JsonConvert.DeserializeObject<List<string>>(json);
}
}
}
public class BTableViewDataSource : UITableViewDataSource
{
IList<string> data;
public BTableViewDataSource(IList<string> list) {
data = list;
}
~BTableViewDataSource() {
Console.WriteLine("it was called ");
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
// if cell is not available in reuse pool, iOS will create one automatically
// no need to do null check and create cell manually
var cell = (BookCell)tableView.DequeueReusableCell("BookCell", indexPath) as BookCell;
cell.PopulateCell(data[indexPath.Row], "");
cell.SetNeedsLayout();
//cell.SeparatorInset = UIEdgeInsets.Zero;
return cell;
}
public override nint RowsInSection(UITableView tableView, nint section)
{
return data.Count;
}
}
public class TableDelegate : UITableViewDelegate {
IList<string> data;
BibleHomeController owner;
public TableDelegate(IList<string> list, BibleHomeController reference)
{
owner = reference;
data = list;
}
~TableDelegate()
{
Console.WriteLine("it was called ");
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
//base.RowSelected(tableView, indexPath);
var board = UIStoryboard.FromName("Main", NSBundle.MainBundle);
var vc = (BibleChapterCollectionview)board.InstantiateViewController("BibleChapterCollectionview") as BibleChapterCollectionview;
vc.itemName = data[indexPath.Row];
owner.NavigationController.PushViewController(vc, true);
}
}
My problem is , when I pop the controller in BibleViewController ,
Destructor of none of the classes are called neither dispose is called thereby controller memory is not released .
so everytime i push and pop I add some memory to heap .
I would like to point out , I am detaching all the event handlers from button in viewDidDisappear method .
Could you please help me how to release the resources when I pop the controller .
EDIT :
I had figured the problem is with the tableview.delegate and table.datasource lines.
if I comment them problem is solved .
Should I use weakDelegate?
Modifying this part of code has worked for me .
private void HandleBackClick(object sender, EventArgs e)
{
tableView.Delegate = null;
tableView.DataSource = null;
tableView.Source = null;
this.NavigationController.PopViewController(true);
}
//below modification is not related to problem statement but was needed as buttons need not be nulled
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
backBtn.TouchUpInside -= HandleBackClick;
nwBtn.TouchUpInside -= newBtn;
oldBtn.TouchUpInside -= oldBtnHanle;
}

Resources