I need to call a Xamarin.Forms Page through an Activity in Xamarin.Android.
I had used [Java.Interop.Export("btnOne1Click")] to call the Xamarin.Forms Page.
The code runs without errors if I use App.Current.MainPage = new RootPage(); But it does not navigate to that Page.
I don't want to call the [Java.Interop.Export("btnOne1Click")] on MainActivity.
[Activity(Label = "FailureActivity")]
public class FailureActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
RequestWindowFeature(WindowFeatures.NoTitle);
SetContentView(Resource.Layout.Failure);
Button button = FindViewById<Button>(Resource.Id.txt_go_onjobs);
}
[Java.Interop.Export("btnOne1Click")]
public void btnOne1Click(Android.Views.View v)
{
try
{
Xamarin.Forms.Forms.Context.StartActivity(typeof(RootPage));
}
catch(Exception ex)
{ }
}
}
Xamarin forms runs on one activity, which is most like your main activity.
There are two sample projects that show you how to communicate between native and form parts of the code, which can be found here:
https://github.com/xamarin/xamarin-forms-samples/tree/master/Forms2Native
https://github.com/xamarin/xamarin-forms-samples/tree/master/Native2Forms
However, to answer your question, you would do something like the following:
button = FindViewById<Button> (Resource.Id.txt_go_onjobs);
button.Click += (sender, e) => {
// this is your Xamarin.Forms screen
StartActivity(typeof(FormsActivity));
};
Now, in your FormsActivity call this:
[Activity (Label = "FormsActivity")]
public class FormsActivity : Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
Xamarin.Forms.Forms.Init (this, bundle);
LoadApplication (new App ());
}
}
Related
Using Xam.Plugins.Notifier for Local Notifications I am able to show a notification in Android. However, when I tap the notification, it reloads the application. Similar to how a remote notification would.
I handle the OnNewIntent() in the MainActivitiy.cs but it never fires.
How do I tap a Local Notification made by Xam.Plugins.Notifier so that OnNewIntent() fires and I can show a Shell item?
protected async override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var title = intent.GetStringExtra("title");
if (title != null)
{
await Shell.Current.GoToAsync("Tools/Sales");
}
}
I have a SplashScreen Activity that actually starts the app:
How do I pass the Intent that starts the activity to the MainActitity from the Splash
[Activity(Theme = "#style/MyTheme.Splash", MainLauncher = true, NoHistory = true, LaunchMode = Android.Content.PM.LaunchMode.SingleTop)]
public class SplashActivity : AppCompatActivity
{
public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)
{
base.OnCreate(savedInstanceState, persistentState);
}
// Launches the startup task
protected override void OnResume()
{
base.OnResume();
StartActivity(new Intent(Application.Context, typeof(MainActivity)));
}
public override void OnBackPressed() { }
}
On the MainActivty class, set its LaunchMode to SingleTop so the OS will reuse the activity IF it is all already running vs. starting a new one:
[Activity(Label = "Your Xamarin Forms App", MainLauncher = true, Icon = "#mipmap/icon", LaunchMode = Android.Content.PM.LaunchMode.SingleTop)]
NOTE: There are two entry points into MainActvity for the notification intent
a. If the activity is already running, OnNewIntent will be called with the intent that you setup on the notification.
b. If your app is not running, MainActvity will be created as a normal startup but will include the intent from from the notification, so check the intent in the OnCreate.
in my Xamarin.Forms project I use OneSignal for notifications. In iOS Xamarin.Forms.Application.Current.MainPage = new NavigationPage(new NotificationPage()); worked but in Android this not work. I tried to use messaging center to communicate with PCL project. It worked when app is background but not working when app is closed. How can I redirect notification specific page when notification received in Android? Thanks
Note : Code edited and issue solved, I used shared preferences to control if app launched from notification or not. Then I Load xamarin.Forms application.
You can use below method before LoadApplication() method is called.
if (Intent.Extras != null)
{
foreach (var key in Intent.Extras.KeySet())
{
if (key != null)
{
var value = Intent.Extras.GetString(key);
Log.Debug(TAG, "Key: {0} Value: {1}", key, value);
}
}
}
LoadApplication(new App());
You have to set intent.putExtra() method in OnMessageReceivedMethod().
intent.PutExtra("Key", "value");
Then you can use redirection in App.xaml.cs file based on this key value. Because in android when notification is open while app is reinitialized.
I think you don't need to put below condition.
if (extrasList[0] == "true")
{
LoadApplication(new App(true));
}
else
{
LoadApplication(new App(false));
}
First Store your value global level so you can use it in App.cs file. Just use below code to handle page navigation in App() class like.
if (Device.RuntimePlatform == Device.Android)
{
if (YourKey == "true")
{
//handle that page navigation
}
else
{
//Default Page of App
}
}
Note : Code edited and issue solved, I used shared preferences to control if app launched from notification or not. Then I Load xamarin.Forms application
public class MainActivity :
global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
InitializeUI();
global::Xamarin.Forms.Forms.Init(this, bundle);
global::Xamarin.FormsMaps.Init(this, bundle);
ImageCircleRenderer.Init();
tV = new TextView(this);
resources = this.Resources;
OneSignal.Current.StartInit("***APP ID***")
.InFocusDisplaying(OSInFocusDisplayOption.Notification)
.HandleNotificationReceived(HandleNotificationReceived)
.HandleNotificationOpened(HandleNotificationOpened)
.EndInit();
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
var LaunchFromNotification = prefs.GetString("is_notification_received", "false");
if (LaunchFromNotification == "true")
{
LoadApplication(new App(true));
}
else
{
LoadApplication(new App(false));
}
OneSignal.Current.IdsAvailable(IdsAvailable); //Lets you retrieve the OneSignal player id and push token.
}
}
private static void HandleNotificationOpened(OSNotificationOpenedResult result)
{
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(Android.App.Application.Context);
ISharedPreferencesEditor editor = prefs.Edit();
editor.PutString("is_notification_received", "true");
editor.Apply();
}
protected override void OnResume()
{
base.OnResume();
ISharedPreferences prefs =
PreferenceManager.GetDefaultSharedPreferences(this);
ISharedPreferencesEditor editor = prefs.Edit();
editor.Remove("is_notification_received");
editor.PutString("is_notification_received", "false");
editor.Apply();
}
I'm a newbie to Xamarin. I have created an application that uses DrawerLayout(Android). But my problem is that every time i select a item in the menu(DrawerLayout menu), the memory increases, and this causes the app to become slow and crush. I've tried to use Xamarin profiler to analyze memory leaks - it suspects something called String.FastAllocationString, but it doesn't really show the line(code) that causes String.FastAllocationString issue. Please help ? Here is my code :
MainActivity
DrawerLayout drawerLayout;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
drawerLayout = FindViewById<DrawerLayout>(Resource.Id.drawer_layout);
// Init toolbar
var toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.app_bar);
SetSupportActionBar(toolbar);
SupportActionBar.SetTitle(Resource.String.app_name);
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
SupportActionBar.SetDisplayShowHomeEnabled(true);
// Attach item selected handler to navigation view
var navigationView = FindViewById<NavigationView>(Resource.Id.nav_view);
navigationView.NavigationItemSelected += NavigationView_NavigationItemSelected;
// Create ActionBarDrawerToggle button and add it to the toolbar
var drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, Resource.String.open_drawer, Resource.String.close_drawer);
drawerLayout.SetDrawerListener(drawerToggle);
drawerToggle.SyncState();
}
void NavigationView_NavigationItemSelected(object sender, NavigationView.NavigationItemSelectedEventArgs e)
{
var ft = FragmentManager.BeginTransaction();
ft.AddToBackStack(null);
switch (e.MenuItem.ItemId)
{
case (Resource.Id.nav_incidents):
SupportActionBar.SetTitle(Resource.String.toolbar_Test);
ft.Add(Resource.Id.HomeFrameLayout, new Test());
break;
}
ft.Commit();
ft.Dispose();
// Close drawer
drawerLayout.CloseDrawers();
}
Fragment
[Activity(Label = "Test")]
public class Test : Fragment
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.Inflate(Resource.Layout.Test, container, false);
return view;
}
}
Xamarin Profiler
you have to check fragment is available before you add new one
switch (e.MenuItem.ItemId)
{
case (Resource.Id.nav_incidents):
SupportActionBar.SetTitle(Resource.String.toolbar_Test);
Fragment myFragment =
(Fragment)FragmentManager.FindFragmentByTag("FRAGMENT1");
if (myFragment.IsVisible){
ft.Replace(Resource.Id.HomeFrameLayout, new Test(),"FRAGMENT1");
}else{
ft.Add(Resource.Id.HomeFrameLayout, new Test(),"FRAGMENT1");
}
break;
}
Hope this help
In Xamarin Forms, when I use the following code:
public SomePage()
{
InitializeComponent();
someEntry.Focus();
}
the code entry isn't focused by default, however, if I use the following code:
protected override void OnAppearing()
{
base.OnAppearing();
someEntry.Focus();
}
it works as needed (entry is focused). Why is that? Isn't codeEntry already existing and sitting at it's place, fully functional, after InitializeComponent() call? I mean, I sure can change Text property from page constructor.
In Xamarin, every control has equivalent view renderer, that is native UI element which will only be created when control is added to the native element hierarchy. In constructor, native element for entry is not yet created. However, in OnAppering, entry's corresponding native element is created so it can get the focus.
Also this seems like a bug as Xamarin is storing state and applying it when creating the native UI element. Its time to file a bug !!!
When I use Shell this don't work anymore.
protected override void OnAppearing()
{
base.OnAppearing();
someEntry.Focus();
}
But this does work:
protected async override void OnAppearing()
{
base.OnAppearing();
await Task.Delay(100);
someEntry.Focus();
}
File.xaml
<Entry x:Name="txtLPN" Placeholder="Scan LPN." Grid.Row="1" Grid.Column="0" FontSize="15" Focused="txtLPN_Focused" />
File.cs >>>>>
private void txtLPN_Focused(object sender, FocusEventArgs e)
{
txtLPN.CursorPosition = 0;
if (!string.IsNullOrEmpty(txtLPN.Text))
txtLPN.SelectionLength = txtLPN.Text.Length;
}
protected async override void OnAppearing()
{
base.OnAppearing();
await Task.Delay(600);
txtLPN.Focus();
}
I tried all of the above. I think because my page is a pop-up and has some animation none of the above worked. However this worked for me:
BackgroundWorker setFocus = new BackgroundWorker();
In constructor
setFocus.DoWork += SetFocus_DoWork;
private void SetFocus_DoWork(object sender, DoWorkEventArgs e)
{
bool worked = false;
while (!worked)//will keep trying until it can set focus (when MyEntry is rendered)
{
Thread.Sleep(1);
MainThread.InvokeOnMainThreadAsync(()=> worked = MyEntry.Focus());
}
}
protected override void OnAppearing()
{
base.OnAppearing();
if(!setFocus.IsBusy)
{
setFocus.RunWorkerAsync();
}
}
You may want to add something to handle if "worked" is never set to true like try this for a few seconds.
You can use the Xamarin Community Toolkit LifecycleEffect to call some code when the renderer for the Entry is initialized/cleaned up. Combine this with OnAppearing to reliably show the keyboard without using cheap DoEvents hacks like await Task.Yield() or await Task.Delay(100).
XAML:
<Entry x:Name="userName">
<Entry.Effects>
<xct:LifecycleEffect Loaded="LifecycleEffect_Loaded" Unloaded="LifecycleEffect_Unloaded" />
</Entry.Effects>
</Entry>
C#:
private bool userNameLoaded = false;
protected override void OnAppearing()
{
base.OnAppearing();
if (userNameLoaded)
{
userName.Focus();
}
}
private void LifecycleEffect_Loaded(object sender, System.EventArgs e)
{
if (sender == userName)
{
userNameLoaded = true;
userName.Focus();
}
}
private void LifecycleEffect_Unloaded(object sender, System.EventArgs e)
{
if (sender == userName)
{
userNameLoaded = false;
}
}
OnAppearing won't be called after background + resume on iOS, so you'll need to hook into Application.OnResume to show focus if the user backgrounds + restores the app on iOS:
protected override void OnResume()
{
if (Xamarin.Forms.Device.RuntimePlatform == Xamarin.Forms.Device.iOS)
{
// TODO: Use Messenger, check Shell.Current.CurrentPage, etc. to set focus.
}
}
underneath of Xamarin form it is android activity or iOS 's UIviewController's page life cycle works. someEntry.Focus(); will not work in your constructor
I tried to use the back navigation by overriding OnBackButtonPressed, but somehow it wasn't get called at all. I am using the ContentPage and the latest 1.4.2 release.
Alright, after many hours I figured this one out. There are three parts to it.
#1 Handling the hardware back button on android. This one is easy, override OnBackButtonPressed. Remember, this is for a hardware back button and android only. It will not handle the navigation bar back button. As you can see, I was trying to back through a browser before backing out of the page, but you can put whatever logic you need in.
protected override bool OnBackButtonPressed()
{
if (_browser.CanGoBack)
{
_browser.GoBack();
return true;
}
else
{
//await Navigation.PopAsync(true);
base.OnBackButtonPressed();
return true;
}
}
#2 iOS navigation back button. This one was really tricky, if you look around the web you'll find a couple examples of replacing the back button with a new custom button, but it's almost impossible to get it to look like your other pages. In this case I made a transparent button that sits on top of the normal button.
[assembly: ExportRenderer(typeof(MyAdvantagePage), typeof
(MyAdvantagePageRenderer))]
namespace Advantage.MyAdvantage.MobileApp.iOS.Renderers
{
public class MyAdvantagePageRenderer : Xamarin.Forms.Platform.iOS.PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (((MyAdvantagePage)Element).EnableBackButtonOverride)
{
SetCustomBackButton();
}
}
private void SetCustomBackButton()
{
UIButton btn = new UIButton();
btn.Frame = new CGRect(0, 0, 50, 40);
btn.BackgroundColor = UIColor.Clear;
btn.TouchDown += (sender, e) =>
{
// Whatever your custom back button click handling
if (((MyAdvantagePage)Element)?.
CustomBackButtonAction != null)
{
((MyAdvantagePage)Element)?.
CustomBackButtonAction.Invoke();
}
};
NavigationController.NavigationBar.AddSubview(btn);
}
}
}
Android, is tricky. In older versions and future versions of Forms once fixed, you can simply override the OnOptionsItemselected like this
public override bool OnOptionsItemSelected(IMenuItem item)
{
// check if the current item id
// is equals to the back button id
if (item.ItemId == 16908332)
{
// retrieve the current xamarin forms page instance
var currentpage = (MyAdvantagePage)
Xamarin.Forms.Application.
Current.MainPage.Navigation.
NavigationStack.LastOrDefault();
// check if the page has subscribed to
// the custom back button event
if (currentpage?.CustomBackButtonAction != null)
{
// invoke the Custom back button action
currentpage?.CustomBackButtonAction.Invoke();
// and disable the default back button action
return false;
}
// if its not subscribed then go ahead
// with the default back button action
return base.OnOptionsItemSelected(item);
}
else
{
// since its not the back button
//click, pass the event to the base
return base.OnOptionsItemSelected(item);
}
}
However, if you are using FormsAppCompatActivity, then you need to add onto your OnCreate in MainActivity this to set your toolbar:
Android.Support.V7.Widget.Toolbar toolbar = this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
But wait! If you have too old a version of .Forms or too new version, a bug will come up where toolbar is null. If this happens, the hacked together way I got it to work to make a deadline is like this. In OnCreate in MainActivity:
MobileApp.Pages.Articles.ArticleDetail.androdAction = () =>
{
Android.Support.V7.Widget.Toolbar toolbar = this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
};
ArticleDetail is a Page, and androidAction is an Action that I run on OnAppearing if the Platform is Android on my page. By this point in your app, toolbar will no longer be null.
Couple more steps, the iOS render we made above uses properties that you need to add to whatever page you are making the renderer for. I was making it for my MyAdvantagePage class that I made, which implements ContentPage . So in my MyAdvantagePage class I added
public Action CustomBackButtonAction { get; set; }
public static readonly BindableProperty EnableBackButtonOverrideProperty =
BindableProperty.Create(
nameof(EnableBackButtonOverride),
typeof(bool),
typeof(MyAdvantagePage),
false);
/// <summary>
/// Gets or Sets Custom Back button overriding state
/// </summary>
public bool EnableBackButtonOverride
{
get
{
return (bool)GetValue(EnableBackButtonOverrideProperty);
}
set
{
SetValue(EnableBackButtonOverrideProperty, value);
}
}
Now that that is all done, on any of my MyAdvantagePage I can add this
:
this.EnableBackButtonOverride = true;
this.CustomBackButtonAction = async () =>
{
if (_browser.CanGoBack)
{
_browser.GoBack();
}
else
{
await Navigation.PopAsync(true);
}
};
That should be everything to get it to work on Android hardware back, and navigation back for both android and iOS.
You are right, in your page class override OnBackButtonPressed and return true if you want to prevent navigation. It works fine for me and I have the same version.
protected override bool OnBackButtonPressed()
{
if (Condition)
return true;
return base.OnBackButtonPressed();
}
Depending on what exactly you are looking for (I would not recommend using this if you simply want to cancel back button navigation), OnDisappearing may be another option:
protected override void OnDisappearing()
{
//back button logic here
}
OnBackButtonPressed() this will be called when a hardware back button is pressed as in android. This will not work on the software back button press as in ios.
Additional to Kyle Answer
Set
Inside YOURPAGE
public static Action SetToolbar;
YOURPAGE OnAppearing
if (Device.RuntimePlatform == Device.Android)
{
SetToolbar.Invoke();
}
MainActivity
YOURPAGE.SetToolbar = () =>
{
Android.Support.V7.Widget.Toolbar toolbar =
this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
};
I use Prism libray and for handle the back button/action I extend INavigatedAware interface of Prism on my page and I implement this methods:
public void OnNavigatedFrom(INavigationParameters parameters)
{
if (parameters.GetNavigationMode() == NavigationMode.Back)
{
//Your code
}
}
public void OnNavigatedTo(INavigationParameters parameters)
{
}
Method OnNavigatedFrom is raised when user press back button from Navigation Bar (Android & iOS) and when user press Hardware back button (only for Android).
For anyone still fighting with this issue - basically you cannot intercept back navigation cross-platform. Having said that there are two approaches that effectively solve the problem:
Hide the NavigationPage back button with NavigationPage.ShowHasBackButton(this, false) and push a modal page that has a custom Back/Cancel/Close button
Intercept the back navigation natively for each platform. This is a good article that does it for iOS and Android: https://theconfuzedsourcecode.wordpress.com/2017/03/12/lets-override-navigation-bar-back-button-click-in-xamarin-forms/
For UWP you are on your own :)
Edit:
Well, not anymore since I did it :) It actually turned out to be pretty easy – there is just one back button and it’s supported by Forms so you just have to override ContentPage’s OnBackButtonPressed:
protected override bool OnBackButtonPressed()
{
if (Device.RuntimePlatform.Equals(Device.UWP))
{
OnClosePageRequested();
return true;
}
else
{
base.OnBackButtonPressed();
return false;
}
}
async void OnClosePageRequested()
{
var tdvm = (TaskDetailsViewModel)BindingContext;
if (tdvm.CanSaveTask())
{
var result = await DisplayAlert("Wait", "You have unsaved changes! Are you sure you want to go back?", "Discard changes", "Cancel");
if (result)
{
tdvm.DiscardChanges();
await Navigation.PopAsync(true);
}
}
else
{
await Navigation.PopAsync(true);
}
}
protected override bool OnBackButtonPressed()
{
base.OnBackButtonPressed();
return true;
}
base.OnBackButtonPressed() returns false on click of hardware back button.
In order to prevent operation of back button or prevent navigation to previous page. the overriding function should be returned as true. On return true, it stays on the current xamarin form page and state of page is also maintained.
The trick is to implement your own navigation page that inherits from NavigationPage. It has the appropriate events Pushed, Popped and PoppedToRoot.
A sample implementation could look like this:
public class PageLifetimeSupportingNavigationPage : NavigationPage
{
public PageLifetimeSupportingNavigationPage(Page content)
: base(content)
{
Init();
}
private void Init()
{
Pushed += (sender, e) => OpenPage(e.Page);
Popped += (sender, e) => ClosePage(e.Page);
PoppedToRoot += (sender, e) =>
{
var args = e as PoppedToRootEventArgs;
if (args == null)
return;
foreach (var page in args.PoppedPages.Reverse())
ClosePage(page);
};
}
private static void OpenPage(Page page)
{
if (page is IPageLifetime navpage)
navpage.OnOpening();
}
private static void ClosePage(Page page)
{
if (page is IPageLifetime navpage)
navpage.OnClosed();
page.BindingContext = null;
}
}
Pages would implement the following interface:
public interface IPageLifetime
{
void OnOpening();
void OnClosed();
}
This interface could be implemented in a base class for all pages and then delegate it's calls to it's view model.
The navigation page and could be created like this:
var navigationPage = new PageLifetimeSupportingNavigationPage(new MainPage());
MainPage would be the root page to show.
Of course you could also just use NavigationPage in the first place and subscribe to it's events without inheriting from it.
Maybe this can be usefull, You need to hide the back button, and then replace with your own button:
public static UIViewController AddBackButton(this UIViewController controller, EventHandler ev){
controller.NavigationItem.HidesBackButton = true;
var btn = new UIBarButtonItem(UIImage.FromFile("myIcon.png"), UIBarButtonItemStyle.Plain, ev);
UIBarButtonItem[] items = new[] { btn };
controller.NavigationItem.LeftBarButtonItems = items;
return controller;
}
public static UIViewController DeleteBack(this UIViewController controller)
{
controller.NavigationItem.LeftBarButtonItems = null;
return controller;
}
Then call them into these methods:
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.AddBackButton(DoSomething);
UpdateFrames();
}
public override void ViewWillDisappear(Boolean animated)
{
this.DeleteBackButton();
}
public void DoSomething(object sender, EventArgs e)
{
//Do a barrel roll
}
Another way around is to use Rg.Plugins.Popup Which allows you to implement nice popup. It uses another NavigationStack => Rg.Plugins.Popup.Services.PopupNavigation.Instance.PopupStack. So your page won't be wrap around the NavigationBar.
In your case I would simply
Create a full page popup with opaque background
Override ↩️ OnBackButtonPressed for Android on ⚠️ParentPage⚠️ with something like this:
protected override bool OnBackButtonPressed()
{
return Rg.Plugins.Popup.Services.PopupNavigation.Instance.PopupStack.Any();
}
Since the back-button affect the usual NavigationStack your parent would pop out whenever the user try to use it while your "popup is showing".
Now what? Xaml what ever you want to properly close your popup with all the check you want.
💥 Problem solved for these targets💥
[x] Android
[x] iOS
[-] Windows Phone (Obsolete. Use v1.1.0-pre5 if WP is needed)
[x] UWP (Min Target: 10.0.16299)