I have a need to enable and disable menu items based on the API call response and it has to be called everytime the menu shows up.
I need asynctask because I have to show progressbar
#Override
public boolean onPrepareOptionsMenu (Menu menu) {
handleMenuItems(menu)
}
private void handleMenuItems(menu)
{
new AsyncTask<Void, Void, Void>(){
#Override
protected Void doInBackground(Void... params) {
//API call
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if (progressDialog != null)
progressDialog.hide();
//necessary menu items are enabled and disabled
super.onPrepareOptionsMenu(menu);
}
#Override
protected void onPreExecute() {
if (progressDialog != null){
progressDialog.setMessage("Checking");
progressDialog.show();
}
super.onPreExecute();
}
}.execute();
}
Whenever I touch the options menu the onPrepareOptionsMenu gets called and the options menu doesn't appear then when I press it again the onPrepareOptionsMenu doesn't get called and the options menu appears.
I want the api to get called every time and menu to be shown whenever I touch for options menu.
You shouldn't show and hide progress dialog in OnPrepareOptionsMenu
Comment out the progress dialog codings and rest of the codes are ok and works as you expected
Note:
You have called super.OnPrepareOptionsMenu in OnPostExecute(UI
Thread)
As the progress bar is not needed don't remove the AsyncTask
otherwise app my crash
I don't think this code does what you expect it to do. When onPrepareOptionsMenu is called the first time, it only schedules the AsyncTask to run at a future time and not right away. Next it executes the super.onPrepareOptionsMenu line which should show whatever the option menu is set to be at that time. I think (have to check on that) the UI will then wait for the user input as it is showing some sort of a menu (maybe not what you had expected). When the menu is touched for the second time, I think it will dismiss the menu and that might be the time that the AsyncTask is executed which results in the first menu that you had expected in the first place. The second instance of the AsyncTask will not run until the first one finishes (unless executeOnExecutor is called instead of execute, but that is not the case here nor I believe it to help the expected outcome.)
You might want to have a "dummy" menu prepared (maybe something that shows "waiting...") and start a background thread where it builds up the right menu and then when it is ready update and display a new menu (programmatically).
I personally would advise in finding a way to build and show the menu on the UI thread (maybe prep the menu as the changes are made, ahead of time, and just show it when needed.
Kaamel
Related
Is there any way to detect the press of the back button of the Navigation Page in Xamarin forms?
You can override your navigation page "OnBackButtonPressed" method:
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(async () =>
{
if (await DisplayAlert("Exit?", "Are you sure you want to exit from this page?", "Yes", "No"))
{
base.OnBackButtonPressed();
await App.Navigation.PopAsync();
}
});
return true;
}
If you are using the shell, you can override the Shell's OnNavigating event:
void OnNavigating(object sender, ShellNavigatingEventArgs e)
{
// Cancel back navigation if data is unsaved
if (e.Source == ShellNavigationSource.Pop && !dataSaved)
{
e.Cancel();
}
}
Update:
OnBackButtonPressed event will get fired ONLY on Android when user press the Hardware back button.
Seems like you are more interested to implement when any page get disappeared you want to do something!
In that case:
You have the page's two methods -
protected override void OnAppearing()
{
base.OnAppearing();
Console.WriteLine("Hey, Im coming to your screen");
}
protected override void OnDisappearing()
{
base.OnDisappearing();
Console.WriteLine("Hey, Im going from your screen");
}
You can override those 2 methods on any page to track when they appear and disappear.
Recent updates to Xamarin forms mean you can now do this in an application made with Shell Navigation for navigation back arrow on both platforms.
Use the Shell.SetBackButtonBehavior method, for example running this code in the constructor of your page object will allow the back navigation to take place only when the bound viewmodel is not busy:
Shell.SetBackButtonBehavior(this, new BackButtonBehavior
{
Command = new Command(async() =>
{
if (ViewModel.IsNotBusy)
{
await Shell.Current.Navigation.PopAsync();
}
})
});
In the body of the Command you can do whatever you need to do when you are intercepting the click of the back button.
Note that this will affect only the navigation back button, not the Android hardware back button - that will need handling separately as per the answers above. You could write a shared method called from both the back button pressed override and the command on shell back button behaviour places to share the logic.
You must override native navigationbar button behavior with custom renderer. OnBackButtonPressed triggers only physical device button. You can read good article how to achive this here
I have a Xamarin.Forms application which uses a TabbedPage, let's call it T, T consists of 3 ContentPage children A, B and C. Since the usere has the possibility to edit some data on tab B, I want to notify user before leaving tab in order to allow him to cancel the navigation change and save changes first or to discard changes and leave. So far I have managed to override OnBackButtonPressed() method and the navigation bar back button (which would exit TabbedPage). However I quickly noticed that I am still loosing changes when switching between tabs. I would like to override the click on new tab, so I could first present user with the leaving dialog and the skip the change or continue with it. What would be the best way to do this? I am currently working only on Android platform, so solutions on the platform level are also acceptible.
Thank you for your suggestions and feedback :)
I do not think there is an easy way to do this ,
you can use OnDissappearing and OnAppearing for the pages, that is as easy as it gets .
However I think you are using the wrong design.
Having tabs are ment to make it easier to navigate between pages, if you are going to notify the user when changing the tabs then it would be annoying . If I were you i would save the data for each page locally. so when you get back to the page you will have the data anyway.
So in the end I followed the advice of Ahmad and implemented the persisting of data on individual tabs so they are not lost when tabs are switched. (I no longer refresh input fields from data from model when OnAppearing is called).
But in order to know if there are some unsaved changes on my ChildB page, I had to implement the following procedures:
I created the method HandleExit on my ChildB page, which checks for unsaved changes in fields (at least one value in input fields is different from the ones in stored model) and the either prompts the user that there are unsaved changes (if there are some) or pops the navigation stack if there are no changes.
private async Task HandleExit()
{
if(HasUnsavedChanges())
{
var action = await DisplayAlert("Alert", "There are unsaved changes, do you want to discard them?", "Discard changes", "Cancel");
if(!action)
{
return;
}
}
await Navigation.PopAsync();
}
Since there are two ways on how user can return from Tabbed page (pressing the back button on device or pressing the back button in navigation bar, I had to:
A: override the back button method on my ChildB page, so it calls the HandleExit method. But since Navigation.PopAsync() needs to be called on UI thread, I had to explicitly execute the method on UI thread as written below:
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(new Action(async () =>
{
await HandleExit();
}));
return true;
}
B: Since there is no way to intercept the navigation bar back button on the ContentPage, I had to intercept the event on the platform level (Android) and then pass the event to the ContentPage if necessary via MessagingCenter. So first we need to intercept the event, when navigation bar button is pressed in one of the child pages and send the event via MessagingCenter. We can do that but adding the following method in our MainActivity.cs class:
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 = Xamarin.Forms.Application.Current.MainPage.Navigation.NavigationStack.LastOrDefault();
var name = currentpage.GetType().Name;
if(name == "ChildA" || name == "ChildB" || name == "ChildC")
{
MessagingCenter.Send("1", "NavigationBack");
return false;
}
}
return base.OnOptionsItemSelected(item);
}
Now whenever we will press the navigation bar back button in one of the child pages (ChildA, ChildB, ChildC) nothing will happen. But the button will work as before on the rest of the pages. For the second part of solution we need to handle the message from MessagingCenter, so we need to subscribe to it in our ChildB page. We can subsribe to the message topic in OnAppearing method as follows:
MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => {
await HandleExit();
});
Be careful to unsubscribe to the topic in OnDisappearing() otherwise strange things could happen, since there will be references left to your ContentPage even if you pop it from your navigation stack.
Now that we have handled both requests for back navigation in our ChildB page, we also need to handle them in all of remaining child pages (ChildA, ChildC), so they will know if there are unsaved changes in ChildB page, even if it is currently not selected. So the solution is again compraised of handling the device back button, and navigation bar back button, but first we heed a way to check if ChildB has unsaved changes when we are on one of the remaining pages, so we again write HandleExit method but this time it is as follows:
private async Task HandleExit()
{
var root = (TabbedPage)this.Parent;
var editPage = root.Children.Where(x => x.GetType() == typeof(ChildB)).FirstOrDefault();
if(editPage != null)
{
var casted = editPage as ChildB;
if (casted.HasUnsavedChanges())
{
var action = await DisplayAlert("Alert", "There are unsaved changes, do you want to discard them?", "Discard changes", "Cancel");
if (!action)
{
return;
}
}
}
await Navigation.PopAsync();
}
The only thing that remains now is to handle both navigation back events inside remaing child pages. The code for them is the same as in the actual ChildB page.
A: Handling the device back button.
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(new Action(async () =>
{
await HandleExit();
}));
return true;
}
B: Subscribing to topic from MessagingCenter
MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => {
await HandleExit();
});
If everthing has been done correctly, we should now be prompted with a dialog on any of the child pages if there are unsaved changes on the ChildB page. I hope this will help somebody in the future :)
It's about TornadoFx' button.
If i use
button("push") {
action {
runAsyncWithProgress {
some()
}
}
}
I see progressindicator on the button.
How can I show/hide it programmatically?
The runAsyncWithProgress function automatically adds and removes the progress indicator so it only shows when the task is running.
The function saves the current graphic property of the node and displays a progress indicator in it's place. When the task is completed, the old graphic is reinstated. You can however modify the graphic property at any time, as long as you do it on the UI thread. Inside of your long running function you can do runLater { graphic = null } to remove the progress indicator for example.
To make it simple, I create a new app, which contains MainPage, and Page2.
MainPage has a button, which navigates to Page2. Also override MainPage.OnBackKeyPress:
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
if (MessageBox.Show("Quit?", "Confirm", MessageBoxButton.OKCancel) == MessageBoxResult.Cancel)
e.Cancel = true;
else
base.OnBackKeyPress(e);
}
Page2 is empty, except override OnNavigateFrom, to simulate a long time operation when back from Page2 to MainPage:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
System.Threading.Thread.Sleep(1000);
base.OnNavigatedFrom(e);
}
Everything is ok, but when I am at Page2, and press Back key 3-4 times very quickly, then I see the message box popup twice.
I expect the message box not shown, or at least show and hide. Is there anything wrong in my code? Thanks.
It depends on what you mean by very quickly...
The code looks OK (apart from the Thread.Sleep which is presumably to simulate a long operation on the UI thread).
I expect this is because while the UI thread is busy (or "sleeping") the back key presses are still registering and being sent to the app, and when the UI thread is available again it will process them one after the other.
This is a good example of why you should avoid blocking the UI thread.
Right now, what I have is, when the user clicks on the page, the page will automatically send the user to a webtask which opens up a pdf file.
What's happening right now is when the user presses the back button, it goes back to the Original page for a split second, before being redirected back to the pdf as I have assigned it to (due to the onnavigateto function)
How would I make it so that, when the user clicks the back button in the pdf document, the app will take the user back to the main page?
Also, on the main page, how do I ensure that the backstack is cleared? (As the Application HAS to exit on the MainPage, so can't go back to the pdf.)
My Code so far, I have tried...
{
public partial class Page2 : PhoneApplicationPage
{
public Page2()
{
InitializeComponent();
}
//as soon as this page is opened, navigate/redirect it to the URL below
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
WebBrowserTask task = new WebBrowserTask() { URL ="http://test.com/test.pdf"};
task.Show();
}
//when the user clicks the hardware back button, instead of taking them to the daily notices, which will send them back to brower
// send the user to the main page
protected override void OnBackKeyPress
(System.ComponentModel.CancelEventArgs e)
{
base.OnBackKeyPress(e);
new Uri("/MainPage.xaml", UriKind.Relative);
}
First of all, why you need the second page that only opens a WebBrowserTask? You can do this from main page.
If you still want to open from second page, you can move WebBrowserTask to constructor and surround it with Dispatcher. This approach is guaranteed that WebBrowserTask will be called only once after navigation to this page (maybe will be some problems with tombstoning). Or, you can save state to PhoneApplicationPage.State to handle where user was and what you should open next.
For clearing back stack you can use next code:
while (NavigationService.BackStack.Any())
{
NavigationService.RemoveBackEntry();
}
You will have to detect this on the application level, rather than the page level. When you 'redirect' the user to the PDF, your application becomes suspended. When they then navigate back, it is resumed.
The Visual Studio template provides a method that it invoked when the application resumes:
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
}
Within the above method, you could set a flag, that you then check when your page is navigated to,, that indicates a resume has occurred.