Xamarin Forms: how not reload ListView when pop back - xamarin

In Xamarin Forms i have a ListView and the following method in Code Behind:
protected async override void OnAppearing()
{
base.OnAppearing();
events.ItemsSource = await App.ServiceManager.GetStream();
}
where
events
is my ListView and fetch data from rest webservice.
When i select an item in the ListView i push a detail page. The problem is that when i pop back to the ListView, the method OnAppearing() is called and make the remote call again.
Instead i'd like that the scroll start from the previous position (before that i push a new page): how can do that?

I think you can simply use a
bool isFirstAppearing = true;
protected async override void OnAppearing()
{
base.OnAppearing();
if(isFirstAppearing) {
isFirstAppearing = false;
events.ItemsSource = await App.ServiceManager.GetStream();
}
}

You can either do a check if its already been loaded, as alessandro Caliaros answer suggests, or an alternative if you want to get fresh data but still be scrolled in the same position, you can add code in onappearing to set the scroll position to be the same as it was before:
var currentYPosition = _scrollView.ScrollY;
await _scrollView.ScrollToAsync(_scrollView.ScrollX, currentYPosition, true);

Related

UI freezes for 2-3 seconds when loading a carouselview

I have a carouselview binded to a viewmodel, on the previous page (call it first page) user can select 2 arguments and with the help of those, the next view (call it second page) is generated accordingly. However, I can't wrap my head around why my view won't load asynchronously.
So my problem: When I click the button on the first page the UI would freeze for like a solid 2-3 seconds, and then start load (asynchronously?) and once it's done it's all good.
Also I couldn't really figure out a better way to inherit values from first page to second so if someone has an idea please let me know.
Any help on how can I fix this I would really appreciate.
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableRangeCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
foreach (var data in filtereddata)
{
FilteredData.Add(data);
}
}
}
The carouselview in second page
<ContentPage.BindingContext>
<db:DataSelectionViewModel/>
</ContentPage.BindingContext>
...
<!-- DataTemplate for carouselview has radiobuttons, label and button all in a grid -->
<CarouselView ItemsSource="{Binding FilteredData}">
Second Page c#
public partial class SecondPage : ContentPage
{
public Coll(bool hard, string subject)
{
InitializeComponent();
var vm = (BaseViewModel)BindingContext;
vm.Hard = hard;
vm.Subject = subject;
/* had to set "hard" and "subject" here again, otherwise data won't load */
}
protected override async void OnAppearing()
{
var vm = (DataSelectionViewModel)BindingContext;
base.OnAppearing();
await vm.LoadData.ExecuteAsync().ConfigureAwait(false);
}
}
The first page view containing the button
<Button x:Name="Start" Pressed="ButtonClick"/>
First page c# --> Here I also tried doing it with a command and a pressed at the same time, because I couldn't come up with a way to save variables to second page viewmodel, that's why I use pressed here
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
await Navigation.PushAsync(new SecondPage(vm.Hard, vm.Subject));
}
I have tried not using the OnAppearing method to get my data, but then it wouldn't bind to the page and it would not show, if I were to previously fill the ObservableCollection with my data and then load the page although I would love to be able to do this because it would allow me to create a loading popup also.

Xamarin Android back button is not working

I had build a Multipager Xamarin App, but after second page Android back button and Navigation back button is not working.
I had tried Shell Backbuttonbehavior , but with this only Top Navigation back button is working
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}" IconOverride="back.png" />
</Shell.BackButtonBehavior>
I had tried overriding OnAppearing and OnDisappearing to set Current and pervious navigation but that too is not triggering on thrid page
protected override void OnAppearing()
{
base.OnAppearing();
Shell.Current.Navigating += Current_Navigating;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
Shell.Current.Navigating -= Current_Navigating;
}
private async void Current_Navigating(object sender, ShellNavigatingEventArgs e)
{
if (Models.PatientPlannerData.moveNext == false)
{
Shell.Current.Navigating -= Current_Navigating;
await Shell.Current.GoToAsync("..", true);
}
}
For Navigation from one page to another I am using
await Shell.Current.GoToAsync(nameof(Page2));
Everything is working fine till second page, from third page back button is not triggering
For the Android back button, after compilation the Navigation Bar that we call in Xamarin Forms, turns into the Action Bar for Android during runtime.
Set the toolbar after LoadApplication(new App()); in OnCreate of MainActivity.
AndroidX.AppCompat.Widget.Toolbar toolbar
= this.FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
And then overrode OnOptionsItemSelected(). When you press the backbutton on navigation bar, this event would be triggered.
public override bool OnOptionsItemSelected(IMenuItem item)
{
}

Detect Back Arrow Press Of The NavigationPage in Xamarin Forms

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

Xamarin Forms Ondisappearing() called soon after Onappearing()

Ondisappearing() called soon after Onappearing() and onAppearing called right after onDisappearing. I had this issue only on one page. other pages do not have this issue. I found this when I tap on navbar back button, it does not navigate to the previous page, I had to tap twice to navigate back on this particular page..then I found this problem. degraded forms version 4.0 to 3.5 and recreated new page did not solve this issue..help need...
public Cart_Page2 ()
{
InitializeComponent ();
}
protected override void OnAppearing()
{
base.OnAppearing();
var vm = new Cart_page_vm();
vm.Navigation = Navigation;
BindingContext = vm;
}
protected override void OnDisappearing()
{
// MessagingCenter.Send<Cart_page>(this, "cart_page_disappear");
// Navigation.PopAsync();
}

Why doesn't Navigation.PopAsync() trigger the OnAppearing method of the underlying page?

My Xamarin Forms Application has a MainPage set to
new NavigationPage(new CarsMasterDetailPage())
where CarsMasterDetailPage : MasterDetailPage.
In CarsMasterDetailPage constructor, I set the Master property to new CarsMasterPage() and the Detail property to new CarsDetailPage(). Both CarsMasterPage and CarsDetailPage extend ContentPage.
The master page contains a list of cars and a button which has an event handler that does:
await Navigation.PushAsync(new AddCar());
The AddCar page has a button with an event handler that does:
await Navigation.PopAsync();
When I first run the app, the OnAppearing method of the master page is called. The first time the navigation pops back to the master page, OnAppearing is called again. Subsequent navigation pushes and pops don't, though.
I can get around it by adding a delegate on the master page that the add car page calls when it's done, but that feels hacky since there are page events to handle this. Does anyone know why it's not working?
In my case, I did it like this and everything works fine:
My MainPage:
MainPage = new MasterDetailPage {
Master = new MasterDetailPage1Master(),
Detail = new MyNavigationPage(new MainPageLinearList(appType))
};
So the key point was to use NavigationPage in a DetailPage. After that everything works fine.
Subscribe to the NavigationPage.Popped event:
navigationPage.Popped += OnPopped;
...
void OnPopped(object sender, NavigationEventArgs e)
{
var currentPage = (sender as NavigationPage).CurrentPage;
var removedPage = e.Page;
...
}

Resources