Xamarin - Android - Activity not removed from activity stack on button click - c#

I have a service that calls a Activity using the following code:
var activityIntent = new Intent(ApplicationContext, typeof(cptChamadaActivity));
activityIntent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTask | ActivityFlags.NoHistory);
StartActivity(activityIntent);
Inside the OnCreate method of the activity, I have that code:
base.OnCreate(bundle);
SetContentView(Resource.Layout.Chamada);
var simBtn = FindViewById<Button>(Resource.Id.simBtn);
var naoBtn = FindViewById<Button>(Resource.Id.naoBtn);
simBtn.Click += (sender, e) => { Finish(); };
naoBtn.Click += (sender, e) => { Finish(); };
If I click on the simBtn, the Activity closes, but is not removed from the activity stack, but, if I put the Finish(); inside the OnCreate(), like that:
base.OnCreate(bundle);
SetContentView(Resource.Layout.Chamada);
var simBtn = FindViewById<Button>(Resource.Id.simBtn);
var naoBtn = FindViewById<Button>(Resource.Id.naoBtn);
simBtn.Click += (sender, e) => { Finish(); };
naoBtn.Click += (sender, e) => { Finish(); };
Finish();
the Activity is closed and removed from the stack.
I'm using the NoHistory = true on my Activity declaration.
Any idea of what I'm missing?

Edit:
I've misunderstood the question. Now in your comment in the original question i think i do now.
The Activity is visible in activity stack on the device itself. I think it is added after OnResume because then all the views are inflated etc making it for Android possible to show it on the stack. If you use Finish(); in OnCreate the views are not visible yet thus not showing up on the stack.
Don't worry, if you finish your activity the activity is actually finished even if Android shows it to the user trough the stack. It's just a means for the user to return to your app.
You can confirm this by overriding OnDestroy and placing a breakpoint. If it's hit then you know the activity is closed. If not, then look at my original answer.
Original answer:
I've ran in the same kind of problem recently. I almost completed my app and during the testing phase other people noticed that Activities were not being disposed. As a result after hours of using the app, the app ran out of memory and hung.
The problem you have probably lies not in your OnCreate but in OnResume as you pass your Activity to another object that is keeping the reference to it thus the Activity not being disposed by mono GC. However without the code in OnResume i can't be 100% sure that you have the same problem i had.
The reason why your example is acting like you described is because when you call Finish(); within OnCreate, OnResume will never be called in the Android lifecycle(Just tested it). So the Activity leak has never occurred. If you use Finish(); within anonymous method then OnResume has already been ran and the code within has already created an Activity leak.
If you like more help with your code please post your OnResume code.
Some tips:
Try avoiding using this and thus passing Activity to other objects. If some object requires a Context, use ApplicationContext instead of this.
If really really need to pass the Activity to other object, create a WeakReference within the object:
public class SomeClass
{
private WeakReference _weakactivity;
private Activity _activity
{
get { return _weakactivity.Target as Activity; }
set { _weakactivity = new WeakReference(value); }
}
public SomeClass(Activity activity)
{
_activity = activity;
}
}
The example will create a weak reference telling the GC it is allowed to dispose it when not used(My understanding of how it works, can be wrong.)
Also another tip is to use Action instead of EventHandlers. As you can use action = null; To discard all events. If you still want to use EventHandlers make sure you unsubscribe before OnDestroy is called.
For more info on this watch https://www.youtube.com/watch?v=88ZLgLrwdts

Managed to do it by creating a simple Activity that just finishes itself after the base.OnCreate(savedInstanceState), and then starting it from the actual Activity I want to close and then Finishing it.
Here is the Activity:
[Activity]
public class clsExitActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Finish();
}
public static void ExitApp(Context ctx)
{
var i = new Intent(ctx, typeof(clsExitActivity));
i.AddFlags(ActivityFlags.NewTask | ActivityFlags.ClearTask | ActivityFlags.ExcludeFromRecents | ActivityFlags.NoAnimation);
ctx.StartActivity(i);
}
}
And then the usage (from the activity I want to close):
base.Finish();
clsExitActivity.ExitApp(ApplicationContext);
I found the code for the clsExitActivity in another question, but I can't find it again to paste it's URL here.

Related

CrossFirebasePushNotification.Current.OnNotificationReceived event is only executed once

I'm using push notification with FCM in a Xamarin Forms application, so everything looks to be working fine except for one specific case.
Used package: Plugin.FirebasePushNotification
The event CrossFirebasePushNotification.Current.OnNotificationReceived += is being called only once when the application is open and when the app starts.
if I send 2 or more notifications from the server it's only called for the first notification, after that stop working. However the notification popup is always shown no matter what, even when the is in foreground, background, killed.
I want this to be called when the app is open because I need to perform an action depending on the notification data.
I'm testing in iOS 15.3.1
Intructions: https://github.com/CrossGeeks/FirebasePushNotificationPlugin/blob/master/docs/GettingStarted.md
Versions:
"Plugin.FirebasePushNotification" Version="3.4.1"
"Xamarin.Forms" Version="5.0.0.2337"
Thanks in advance.
My entire AppDelegate.cs Code:
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
NSObject _onKeyboardShowObserver;
NSObject _onKeyboardHideObserver;
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Rg.Plugins.Popup.Popup.Init();
Xamarin.FormsMaps.Init();
Firebase.Core.App.Configure();
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
RegisterKeyBoardObserver();
FirebasePushNotificationManager.Initialize(options, true);
return base.FinishedLaunching(app, options);
}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
FirebasePushNotificationManager.DidRegisterRemoteNotifications(deviceToken);
}
public override void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
{
FirebasePushNotificationManager.RemoteNotificationRegistrationFailed(error);
}
// To receive notifications in foreground on iOS 9 and below.
// To receive notifications in background in any iOS version
//[Export("application:didReceiveRemoteNotification")]
[Export("messaging:didReceiveRemoteNotification:withCompletionHandler:")]
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
{
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired 'till the user taps on the notification launching the application.
// If you disable method swizzling, you'll need to call this method.
// This lets FCM track message delivery and analytics, which is performed
// automatically with method swizzling enabled.
FirebasePushNotificationManager.DidReceiveMessage(userInfo);
// Do your magic to handle the notification data
System.Console.WriteLine(userInfo);
completionHandler(UIBackgroundFetchResult.NewData);
}
void RegisterKeyBoardObserver()
{
if (_onKeyboardShowObserver == null)
_onKeyboardShowObserver = UIKeyboard.Notifications.ObserveWillShow((object sender, UIKeyboardEventArgs args) =>
{
NSValue result = (NSValue)args.Notification.UserInfo.ObjectForKey(new NSString(UIKeyboard.FrameEndUserInfoKey));
CGSize keyboardSize = result.RectangleFValue.Size;
MessagingCenter.Send<object, KeyboardAppearEventArgs>(this, Constants.iOSKeyboardAppears, new KeyboardAppearEventArgs { KeyboardSize = (float)keyboardSize.Height });
});
if (_onKeyboardHideObserver == null)
_onKeyboardHideObserver = UIKeyboard.Notifications.ObserveWillHide((object sender, UIKeyboardEventArgs args) =>
MessagingCenter.Send<object, string>(this, Constants.iOSKeyboardDisappears, Constants.iOSKeyboardDisappears));
}
public override void WillTerminate(UIApplication application)
{
if (_onKeyboardShowObserver == null)
{
_onKeyboardShowObserver.Dispose();
_onKeyboardShowObserver = null;
}
if (_onKeyboardHideObserver == null)
{
_onKeyboardHideObserver.Dispose();
_onKeyboardHideObserver = null;
}
}
}
Some suggestions for troubleshooting .
As Almis mentioned , place the code into OnStart of App.cs.
Make sure that initialization is correct, check iOS Initialization .
If problem persists , consider raising issue on github .
After different tests I could find what is causing the issue, but I don’t know why.
One of the things I do in the OnNotificationReceived event is show a local Push Notification by calling the NotificationCenter.Current.Show method, and that is what is causing the problem. When I comment the line of code that calls await NotificationCenter.Current.Show(notification) start working fine.
Once the NotificationCenter.Current.Show is executed, then the CrossFirebasePushNotification plugging starts working like if my App is in background or foreground.

Xamarin iOS app freezes: ViewDidAppear() never gets called

This seems to be a tricky one:
We're working on a Xamarin iOS app that has a UITableView control displaying a list of profiles. Once the user clicks on one of the profiles we'd like to switch to a separate "ProfileViewController" to display more detailed information. This works just fine for 99.9% of the time. In the remaining ~0.1% the item in the list gets highlighted (which happens OnClick) but the app just seemingly freezes and never switches to the other ViewController, leaving the user with an unresponsive list.
The interesting thing to note here is that the app doesn't really freeze, as the simple "swipe back" gesture brings the user back to the UITableView (to be clear the switch-to-profile-viewcontroller animation is never played so it basically switches from the unresponsive list back to the same list, but now it is responsive again).
The tricky thing is that we can't reliably reproduce this bug. It just seems to happen at random, sometimes while stuff is running in the background, other times while the app was previously in an idle state. We are pretty sure that it isn't related to multithreading (we triple-checked everything, using locking and semaphores where necessary), it rather seems to be some rendering issue (as you can still swipe back to the previous screen or at least it isn't your common dead lock).
Using a bunch of Console.WriteLine() tracers and hours of trial and error reproducing this bug we could isolate the problem and discovered the following:
Upon clicking on the list code in ViewDidLoad() and ViewWillAppear() is successfully executed. However ViewDidAppear() never gets invoked. There is nothing else happening in our code or on different threads between ViewWillAppear() and ViewDidAppear() and we don't have any funky / unusual code that gets executed previously (just the usual UI initialization like this in ViewDidLoad()):
...
LblProfileName.TextAlignment = UITextAlignment.Center;
LblProfileName.Text = string.Empty;
LblProfileName.Font = FontAgent.ForSize(30);
LblProfileSlogan.TextAlignment = UITextAlignment.Center;
LblProfileSlogan.Text = string.Empty;
LblProfileSlogan.Font = FontAgent.ForSize(20);
LblProfileRelationInfo.TextAlignment = UITextAlignment.Center;
LblProfileRelationInfo.Text = string.Empty;
LblProfileRelationInfo.Font = FontAgent.ForSize(15);
...
At this point we are kinda out of ideas as to what could be going wrong here and we have independently reviewed any of our code that could remotely be involved in this bug but we found nothing.
We didn't find anything related online but maybe someone else has encountered a similar issue like this in Xamarin / Xamarin iOS before?
Are there any steps we could take that we don't know of? When breaking the app in the Visual Studio for Mac debugger during the freezes the call stacks only contain native code and are not of much use.
Any help or ideas as to what else we could try are hugely appreciated :)
Edit: Adding some code
This is our BaseViewController defining some initialization methods to be used by it's children (again in 99.9% of all cases this works just fine):
public abstract class BaseViewController : UIViewController
{
public BaseViewController(string nibName, NSBundle nSBundle) : base(nibName, nSBundle)
{
ControllerDidInitialize();
}
public ClubmappViewController(IntPtr handle) : base(handle)
{
ControllerDidInitialize();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
CustomOrientationService.ScreenRotationPortrait();
InitializeStaticContent();
Console.WriteLine("ViewDidLoad() exits just fine...");
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
Console.WriteLine("When the bug occurs this never gets executed :C");
RefreshUI();
OnViewDidAppear?.Invoke(this, new EventArgs());
LoadData();
}
private protected abstract void InitializeStaticContent();
private protected abstract void RefreshUI();
private protected virtual void LoadData()
{
}
private protected virtual void ControllerDidInitialize()
{
}
}
This is the ProfileViewController that inherits from BaseViewController and that just stops being rendered or inititialized or whatever the actual problem might be (it just never shows up as described above) but most of the time it works just fine:
public partial class ProfileViewController : BaseViewController
{
public const string STORYBOARD_ID = "ProfileViewController";
public ProfileViewController(IntPtr handle) : base(handle)
{
}
private protected override void ControllerDidInitialize()
{
// do initialization things like initializing variables, etc
// no real logic here
}
private protected override void InitializeStaticContent()
{
// layouting loading
LblProfileTitle.TextAlignment = UITextAlignment.Center;
LblProfileTitle.Text = string.Empty;
LblProfileTitle.Font = FontAgent.ForSize(20);
LblProfileTitle.Font = UIFont.BoldSystemFontOfSize(20);
LblProfileName.TextAlignment = UITextAlignment.Center;
LblProfileName.Text = string.Empty;
LblProfileName.Font = FontAgent.ForSize(30);
LblProfileSlogan.TextAlignment = UITextAlignment.Center;
LblProfileSlogan.Text = string.Empty;
LblProfileSlogan.Font = FontAgent.ForSize(20);
// ... and so on ..
}
private protected override void RefreshUI()
{
// ... theme related stuff ...
ViewProfileActive.BackgroundColor = ThemeAgent.CurrentTheme.OnlineIndicatorColor;
LblProfileName.TextColor = ThemeAgent.CurrentTheme.PrimaryTextColor;
LblProfileSlogan.TextColor = ThemeAgent.CurrentTheme.SecondaryTextColor;
// ...
}
private protected async override void LoadData()
{
// load user data ...
ProfileData data = await ...
}
}
And this is the "OnClick" event that is triggered when a profile is clicked on, that's supposed to initialize and show the ProfileViewController (it's unlikely that something's wrong with this but including it nonetheless):
// ...
(sender, e) =>
{
ProfileViewController profileController = (ProfileViewController)UIStoryboard.FromName("Main", null).InstantiateViewController(ProfileViewController.STORYBOARD_ID);
profileController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
currentViewController.NavigationController.PushViewController(profileController, true);
}
// ...

Handler fires to early

I have an device with an integrated bixolon printer in it. I want to create an app to print on the printer. My OnCreate method looks as follow:
protected override void OnCreate(Bundle bundle){
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
printer = new BixolonPrinter(this, new MyHandler(), Looper.MainLooper);
printer.FindUsbPrinters();
//button connect
Button button = FindViewById<Button>(Resource.Id.buttonConnect);
button.Click += delegate {
printer.ConnectUsb();//in the brackets I would need the value of the Handler back once it is available
}; }
My Handler is as follow:
private class MyHandler : Handler
{
public override void HandleMessage(Message msg)
{
switch (msg.What)
{
case BixolonPrinter.MessageUsbDeviceSet:
Console.WriteLine("U S B device::: " + msg.Obj);
//can not return the msg.Obj back to the button event
break;
}
}
}
The problem is the once the instance of the BixolonPrinter is created it immediately fires the Handler. There is no way to bring back the result of the Handler to the button event. To make that problem a litte bit more complicated, the BixolonPrinter is a Java .jar file. So how can I get the result back to the event button?
When you use MainLooper, everything will be executed on main thread. Create HandlerThread for interaction instead. To postpone execution, use Handler.postDelayed() method, or, what is better, use RxJava library

how to close activity android using visual studio and xamarin

i have login, register, and home page on my project. I use StartActivity(typeof(Register));to open register page. When user already insert data and click register button, i use StartActivity(typeof(MainActivity)); to go back to login page again.
When i click back button on my phone it back to register page>login page>then exit. I want my activity that already created is closed after i open a new page.
And my second question, i have exit button, how to close my app using the exit button?
I'm using Visual Studio 2015 and Xamarin for developing android app.
Calling Finish will close and kill the Activity and it will work as expected. Better way to remove an Avtivity so that it won't appear when Back button is pressed will be to set the NoHistory of that Activity as true.
If you have a LoginActivity and a DashboardActivity and you don't want the LoginActivity to show while pressing back-button after logging in, you can set NoHistory of LoginActivity as true, like below.
[Activity (NoHistory = true)]
public class LoginActivity : Activity
{
}
You can use Finish method to close your current activity:
StartActivity(typeof(MainActivity));
Finish();
To close the app, simply use
System.exit(0);
To remove an activity from navigation you can use finish keyword like that :
[Activity(Label = "MainActivity", MainLauncher = true, Icon = "#drawable/icon")]
public class MainActivity: Activity
{
protected override async void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
var intent = new Intent(this, typeof(SecondActivity));
intent.SetFlags(ActivityFlags.NewTask);
//Navigation to SecondActivity
StartActivity(intent);
//delete main activity from navigation
Finish();
}
}
For the Second question you can use :
System.exit(0);
You have a very good explanation about this feature in this post for android that you can use for xamarin android :
Close Android Application
StartActivity(typeof(nameOfActivity));
// add this line
Finish();
You can't close previous activity in current activity. It only can be closed by itself.
But you can return data to previous activity. And in event handler OnActivityResult of previous activity, you can do close action.
This sample will be helpful for you. https://code.msdn.microsoft.com/How-to-close-activity-d51941c8
code below shows how to close previous activity.
In previous activity:
Intent intent = new Intent(this, typeof(RegisterActivity));
//for get result, we should use method StartActivityForResult
//the second param is the request code, it is the ID of this request, it should be >= 0
StartActivityForResult(intent, 1);
In current activity:
Intent intent = new Intent(this, typeof(RegisterActivity));
intent.PutExtra("result", "Success");
SetResult(Result.Ok, intent);
Finish();
//when back to login activity, the OnActivityResult event will be trigger.
And go back to previous activity:
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
//when regester activity retrun data, it will be execute
if (requestCode == 1 && resultCode == Result.Ok)
{
string result = data.GetStringExtra("result");
if (result == "Success")
{
Finish();
}
}
}
For your second question:
Just use this:
System.exit(0);

Caliburn.Micro Go Back called in On Activate not working in WinRT

This question is specific to Windows Phone 8.1 (WinRT); it may also be applicable Windows 8.1. I am using Caliburn.Micro 2.0.1
In my ViewModel's OnActivate I check whether an item is a database, if it isn't, I want to navigate back to the previous page.
The simplist solution will be just to call GoBack in the OnActivate method (this works in Windows Phone 8.0):
INavigationService _navigationService;
protected override void OnActivate()
{
_item = GetItemFromDB();
if(_item == null)
{
_navigationService.GoBack()
}
}
To navigate to the view model I call:
_navigationService.NavigateToViewModel<MyViewModel>(_param);
But it does not work, it ignores the GoBack call and stays on the page which I do not want to view.
When stepping through the code you can see that the GoBack code is called inside the NavigateToViewModel method; I expect this is the reason why it does not work (something to do with a queuing issue maybe?).
I have a very "hacky" solution that involves a timer (that works), but I really despise it since it is prone to threading issues and has the possibility of being called during the NavigateToViewModel call (if it takes long to finish), which will then again not work:
protected override void OnActivate()
{
_item = GetItemFromDB();
if(_item == null)
{
DispatcherTimer navigateBackTimer = new DispatcherTimer();
navigateBackTimer.Interval = TimeSpan.FromMilliseconds(300);
navigateBackTimer.Tick += GoBackAfterNavigation;
navigateBackTimer.Start();
}
}
public void GoBackAfterNavigation(object sender, object e)
{
_navigationService.GoBack();
(sender as DispatcherTimer).Stop();
}
Is there a better way to navigate back? Why doesn't the GoBack work in OnActivate? Is there a way to get it to work in OnActivate?
You can use
Execute.OnUIThreadAsync(() => /* navigationCode */);
instead of a timer to queue the action immediately after the processing of the current stack has finished.

Categories

Resources