I am newbie to Xamarin.Forms. I am trying to show a DisplayAlert() when NavigationBar back button is clicked. I have tried implementing according to this article. The problem is when I click on the button the popup doesn't come. I placed a breakpoint on OnOptionsItemSelected() method to see if its getting called or not, it doesn't. This is my MainActivity.cs
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
global::Xamarin.FormsMaps.Init(this, bundle);
LoadApplication(new App());
Android.Support.V7.Widget.Toolbar toolbar = this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
//Placed a debugger here
// 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 = Xamarin.Forms.Application.Current.MainPage.Navigation.NavigationStack.LastOrDefault() as NavBackButtonContentPage;
// 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);
}
}
I am using it inside MasterDetailPage.
isaman kumara's comment from 2019-06-01 said the following:
The issue will be fixed when you add following lines to MainActivity
OnCreate method (after the LoadApplication(new App()); line)
Android.Support.V7.Widget.Toolbar toolbar
= this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
One of the responses said this:
This seems to no longer be working. SetSupportActionBar expects a
parameter of type AndroidX.AppCompat.Widget.Toolbar and it won't work
with Android.Support.V7.Widget.Toolbar.
I was able to get OnOptionsItemSelected working again in Xamarin Forms 4.8 by adding this to the MainActivity's OnCreate method after the LoadApplication line:
if (FindViewById(Resource.Id.toolbar) is AndroidX.AppCompat.Widget.Toolbar toolbar)
{
SetSupportActionBar(toolbar);
}
Apologies for not commenting directly to the earlier comment, but I don't have enough reputation points to do so.
I also had the same issue and found the solution. The issue is MainActivity was sub-classing from the FormsAppCompactActivity instead of old FormsApplicationActivity which was the previous parent class for MainActivity. So assume bug on new FormsAppCompactActivity
The issue will be fixed when you add following lines to MainActivity OnCreate method (after the LoadApplication(new App()); line)
Android.Support.V7.Widget.Toolbar toolbar
= this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
Referenced URL and thread as follows
https://forums.xamarin.com/discussion/comment/218663
https://theconfuzedsourcecode.wordpress.com/2017/03/02/formsappcompatactivity-is-not-calling-onoptionsitemselected-xamarin-android/
I know this is an old question, but you can track the OnPopViewAsync event through a NavigationRenderer like this:
[assembly: ExportRenderer(typeof(NavigationPage), typeof(CustomNavigationRenderer))]
namespace YourApp.Droid
{
public class CustomNavigationRenderer : NavigationPageRenderer
{
public CustomNavigationRenderer(Context context) : base(context)
{
}
protected override async Task<bool> OnPopViewAsync(Page page, bool animated)
{
// Write your code here
}
}
}
Using this code, you can capture both events in the same place in Android, the hardware back button and the NavigationBar back button pressed.
Hope this helps
You probably haven't created an event for triggering.
In an contentpage you want to override the back button, try this:
this.CustomBackButtonAction = async () =>
{
var result = await this.DisplayAlert(null,
"Hey wait now! are you sure " +
"you want to go back?",
"Yes go back", "Nope");
if (result)
{
await Navigation.PopAsync(true);
}
};
You will then get the event and a popup asking if you really, really want to go back.
Related
I know that is possible to disable by wrapping a widget with WillPopScope, but it's a pain do this with all widgets.
So, is there a way to Globally Disable Back Button in Flutter?
For Android, you can override onBackPressed in your MainActivity.java (that extends FlutterActivity in your app)
#Override
public void onBackPressed() {
// do nothing
// super.onBackPressed();
}
In Kotlin:
override fun onBackPressed() {
// do nothing
}
Right now, I have an activity with method PrepareData(), used to prepare every data that needed by current activity, this called in OnCreate before I set everything. I call this method, and when find some issue I want to finish current activity.
So this is snippet of my code:
private void PrepareData()
{
try
{
//some code to prepare data here
}
catch(Exception ex)
{
Intent _startNewActivity = new Intent(this, ActivityB);
this.StartActivity(_startNewActivity);
this.Finish();
}
}
and OnCreate like this
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your application here
SetContentView(Resource.Layout.ActivityA);
PrepareData()
toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);
if (toolbar != null)
{
SetSupportActionBar(toolbar);
SupportActionBar.Title = "Activity A";
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
}
}
Right Now, when app find error on PrepareData, intent is called and this.Finish() also called, but somehow app not finish Activity A immediately, it still set toolbar, and also call onResume.
I know there is activity lifecycle that onStop always called after onResume,But I want to know there is way to finish current activity immediately, without call next code?
An activity always calls through the first lifecycle, even if you call finish, e.g. onCreate --> onStart --> onResume. Finish call is only scheduled to be performed after onResume. If your only issue is to prevent some code from executing in onResume, define a flag where you call this.Finish(), for instance bool finishCalled = true; and then to prevent the toolbar from setting the title, just wrap the code inside that bool with if !(finishCalled).
That should do it.
I use button for starting chromecast android.support.v7.app.MediaRouteButton in my app in activities xml and fragments xml with videoplayer.
For initializing cast button I use the next code:
private void setupChromeCast() {
try {
CastButtonFactory.setUpMediaRouteButton(getActivity(), castButton);
castContext = CastContext.getSharedInstance(getActivity());
castSession = castContext.getSessionManager().getCurrentCastSession();
onCastStateChanged(castContext.getCastState());
castSessionManager = new CastSessionManager(this);
isChromeCastAvailable = true;
} catch (Exception e) {
isChromeCastAvailable = false;
}
}
And it works fine in activities. I mean, when chromecast device is near, my MediaRouteButton becomes active and I can press it. But when this Button is on Fragment, it does not become active. And callback
#Override
public void onCastStateChanged(int state)
doesnt call. So, how to fix this bug? And there is one interesting moment: when I`m in fragment, button is not active, but when I hide my app into background, and then open into foreground, my mediaroutebutton becomes active. Its so strange.
In your activity, initialize Cast:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CastContext.getSharedInstance(this);
}
Then in your fragment you can get hold of the Cast context:
#Override
public void onResume() {
super.onResume();
CastContext cc = CastContext.getSharedInstance();
cc.addCastStateListener(this);
cc.getSessionManager().addSessionManagerListener(
this, CastSession.class);
}
Verified with Cast version 18.1.0.
Taken from example docs:
"In order to integrate Google Cast functionality, the Activities need to inherit from either the AppCompatActivity or its parent the FragmentActivity. This limitation exists since we would need to add the MediaRouteButton (provided in the MediaRouter support library) as an MediaRouteActionProvider and this will only work if the activity is inheriting from the above-mentioned classes."
Are you sure you have inherited FragmentActivity or AppCompatActivity?
If you ever got it to work let me know, I want it to work this way too.
I try to use an icon instead of the title on the main page of my Xamarin.Forms app.
I've read a lot of related topics, but I didn't found any solution:
navigationpage-settitleicon
navigationpage-settitleicon-works-on-ios-but-not-android
toolbar-title-set-center-align
xamarin-forms-custom-toolbar-android
navigationpage-settitleicon-works-on-ios-but-not-android
On iOS this is done easily by adding this on my first page:
NavigationPage.SetTitleIcon(this, "navbar_logo.png");
On Android, I've added this on Toolbar.axml:
<ImageView android:src="#drawable/navbar_adp_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
This works, but the icon is now visible on each page on Android...
I've tried to use a renderer to hide the icon when I navigate to another page. For this I would like to use the override OnViewAdded() method:
public class CustomNavigationPageRenderer : NavigationPageRenderer
{
public override void OnViewAdded(Android.Views.View child)
{ ... }
}
I try to use OnViewAdded() cause I've seen in QuickWatch that this method is called each time that I navigate in my Xamarin.Forms app.
I've also noticed that the method is called 2 times during navigation:
the first time, the child parameter is a V7.Widget.Toolbar:
the second time, the child parameter is a Xamarin.Forms.Platform.Android.PageContainer:
So I tried to cast the child into Xamarin.Forms.Platform.Android.PageContainer, but I can't do it because this object is "internal":
PageContainer is inaccessible due to its protection method.
Why couldn't I do it in code, if I can do it with QuickWatch?
I don't see if there is another way to achieve this...
Would you have any suggestion?
You should override the OnElementChanged method and look for changes to the CurrentPage property. That way you'll always know when a specific page is shown and can hide or show the toolbar icon.
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName.Equals("CurrentPage"))
// If we're on MainPage show the icon, otherwise hide it
ChangeToolbarIconVisibility(Element.CurrentPage is MainPage);
}
private void ChangeToolbarIconVisibility(bool visible)
{
var toolbarIcon = FindViewById<ImageView>(Resource.Id.toolbarIcon);
toolbarIcon.Visibility = visible ? Android.Views.ViewStates.Visible : Android.Views.ViewStates.Gone;
}
To make this work, remember to add the id to your modified Toolbar layout:
<ImageView android:src="#drawable/navbar_adp_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:id="#+id/toolbarIcon" />
You can do it by using messaging center and define what you want to do. Let say you want on your MainPage to set an icon and the rest to remove it.You can use something like:
On not main page:
MessagingCenter.Send<Page, string>(this, "toolbar", "notmain");
On main page:
MessagingCenter.Send<Page, string>(this, "toolbar", "main");
You subscribe for those message in your MainActivity like below.
var toolbarIcon = FindViewById<ImageView>(Resource.Id.navbar_adp_logo);
//recommendation: do not use lambda, define a delegate function
MessagingCenter.Subscribe<Page, string>(this, "toolbar", (page, toolbar) =>
{
if (toolbar == "main")
{
toolbarIcon.Visibility = ViewStates.Visible;
}
else
{
toolbarIcon.Visibility = ViewStates.Gone;
}
});
Don't forget to define in xaml
android:id="#+id/navbar_adp_logo"
If you want it only on one page then instead of sending messages from each page you can do the follow once on the page required
protected override void OnDisappearing()
{
base.OnDisappearing();
MessagingCenter.Send<Page, string>(this, "toolbar", "notmain");
}
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Send<Page, string>(this, "toolbar", "main");
}
I want to prevent closing the app by pressing the hardware back button in xamarin forms on android.
I want, that you can navigate with the hardware back button in the app (what is working), but do not want to exit, when the first page in navigation stack is reached.
I tried to use the OnSleep event in xamarin forms, but here I can not cancel the exit.
I also tried catching the back button in android:
public override void OnBackPressed()
{
//base.OnBackPressed();
}
But when using xamarin forms, I do not know which page is currently showing. So I do not know if the navigation back is allowed or not
It works with evaluating the NavigationStack (when you use NavigationPage).
In my Activity, I override the OnBackPressed
public override void OnBackPressed()
{
if(App.Instance.DoBack)
{
base.OnBackPressed();
}
}
In my xamarin forms app (App.Instance (it is a singleton)), I will evaluate the NavigationStack of the current Page like this.
public bool DoBack
{
get
{
NavigationPage mainPage = MainPage as NavigationPage;
if (mainPage != null)
{
return mainPage.Navigation.NavigationStack.Count > 1;
}
return true;
}
}
When there is only one page left in the NavigationStack I will not call base.OnBackPressed, so that I will not close the App.
![test]
And here's what the code could look like for a Xamarin Forms MasterDetail page scenario...
public bool DoBack
{
get
{
MasterDetailPage mainPage = App.Current.MainPage as MasterDetailPage;
if (mainPage != null)
{
bool canDoBack = mainPage.Detail.Navigation.NavigationStack.Count > 1 || mainPage.IsPresented;
// we are on a top level page and the Master menu is NOT showing
if (!canDoBack)
{
// don't exit the app just show the Master menu page
mainPage.IsPresented = true;
return false;
}
else
{
return true;
}
}
return true;
}
}
Just give a blank call in the page where do you wanna prevent, like
protected override bool OnBackButtonPressed()
{
return true;
}
This will prevent the back button in XF-Droid.
Expanding Chris's answer as there is no App.Instance now and one cannot access App in a static manner within platform code.
1. App.xaml.cs in the Shared project
public bool DoBack
{
get
{
return MainPage.Navigation.NavigationStack.Count > 1;
}
}
2.MainActivity.cs in the Android project
Declare a variable in the class:
App app;
In OnCreate(Bundle bundle) change LoadApplication(new App()); to:
app = new App();
LoadApplication(app);
Override the OnBackPressed() method:
public override void OnBackPressed()
{
if (app.DoBack)
{
base.OnBackPressed();
}
}
Here is a solution that works on Android.
Introduce a counter in the application class and increment it with each OnStart and decrement it with each OnStop this way when the counter is 1 you'll know you are at your last activity.
Needless to say, use a base activity implementation so that you don't have to copy-past boiler plate code.
The solution proposed can work nicely, but I don't like "static property exposure" to solve problems. More over, I don't like the usage of "properties as methods" solutions, especially when a lot of logic is involved.
The main problem here how we can handle the OnBackButton() method from our Xamarin.Forms.Application class.
What about doing the same thing in a more elegant way?
First you need to extend the Xamarin.Forms.Application class like this:
namespace YourNameSpace
{
public class Application : Xamarin.Forms.Application
{
#region Public Methods
public bool HandleBackButton()
{
return OnBackPressed();
}
#endregion
#region Application Methods
protected virtual bool OnBackPressed()
{
return false;
}
#endregion
}
}
Your App implementation now will use this class as base class. Remember to modify your xaml and your xaml.cs accordingly:
<common:Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:common="clr-namespace:YourNameSpace"
x:Class="YourNameSpace.App">
</common:Application>
In your App.cs implementation you can now override the OnBackButton() method.
public partial class App : YourNameSpace.Application
{
#region Constructors
public App()
{
InitializeComponent();
}
#endregion
#region App Methods
protected override bool OnBackPressed()
{
// Handle when the back button is pressed
return false;
}
#endregion
}
Then you need to change a little your Activity class implementation.
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
#region Constants and Fields
private App _app;
#endregion
#region Activity Methods
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
_app = new App();
LoadApplication(_app);
}
public override void OnBackPressed()
{
if(!_app.HandleBackButton())
base.OnBackPressed();
}
#endregion
}
You can use this nuget
chd.hwBackButtonManager nuget
github project