I was searching for tricks to make my Xamarin Forms app faster, and I came across this article. Well, I have tried out some options there but I am finding it difficult to achieve the last option that says;
Pre-Load screens: This improved by a lot the performance on the app
itself what we did is load on each screen, on background, the views
and view-models that where needed next, so when pushing them onto the
navigation stack they were already loaded. This reduced by a lot the
time on transition between pages.
This seems to me as a good option and what I have done is to insert pages I maybe needing next onto the Stack before navigating to the page like so; however, I don't know if I am doing this the right way.
public ProductHomePageViewModel()
{
Application.Current.MainPage.Navigation.InsertPageBefore(new TilePage(), Application.Current.MainPage.Navigation.NavigationStack[0]);
}
But this doesn't work well when it is done on a homepage where a click of a back button should exit the app. But in this case clicking the back button navigates to the next page on the stack.
Please assist me on how to pre-load pages. Any advice is good. Thank you!
Like I said in the comments, you could use some kind of Singleton Pattern, but for pages.
I'll pust an example. Consider the following code
public partial class Page1 : ContentPage
{
private static Page1 _myinstance;
public static Page1 myinstance
{
get
{
Prepare();
return _myinstance;
}
}
public static void Prepare()
{
if (_myinstance == null)
{
_myinstance = new Page1();
}
}
public Page1()
{
InitializeComponent();
}
}
For any other Page, you could call Navigation.PushAsync(Page1.Instance) . If Page1 was instanced, it will not run the constructor again, just the first time Instance is called.
But if you want to "Prepare" it beforehand, you could call in a background method the Page1.Prepare()
Remember, the constructor will be executed only once. So the page will be like "Cached"
Related
I currently have a MainWindow that acts as a frame to navigate to other pages in my solution. The problem is, i require one of my pages to be instantiated for the entire duration of my application instead of every time when i navigate to a page, that page gets re-instantiated. I have tried the KeepAlive='true' property for my page but it did not work.
I would like to know if theres a way to implement "this static instance of a page" method for my codes. Thanks. (p.s im not looking or planning to implement the MVVM approach)
public MainWindow()
{
InitializeComponent();
//Instanciate ApiStartup class and Initialize the HTTPClient
ApiStartup.InitializeClient();
Application.Current.MainWindow = this;
Loaded += OnMainWindowLoaded;
}
private void OnMainWindowLoaded(object sender, RoutedEventArgs e)
{
ChangeView(new DetectionPage());
}
public void ChangeView(Page view)
{
MainFrame.NavigationService.Navigate(view);
}
private void quiz_Click(object sender, MouseButtonEventArgs e)
{
var mainWindow = (MainWindow)Application.Current.MainWindow;
mainWindow?.ChangeView(new DetectionPage());
}
If you want to use static value for use in entire application from start to end then you can mention it in app.config file. And easily you can use like ((App)Application.current).KeepAlive in your any xaml file.
A page has the keepalive property.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.page.keepalive?view=netframework-4.8
You may set that to true on whichever page you like.
When you later use navigation methods to navigate to that url, the first instance of the page will be returned.
If that doesn't suit for whatever reason you could cache instances of pages using a dictionary with key type and value page (instance). Then implement some logic decides which pages you do or do not cache in there.
You said you don't want to use mvvm but I think it best to at least mention what almost everyone else does. For other people who intend working in a team then MVVM will be expected and hence for other people reading:
Since pages and frames come with a journal and memory overheads that are often undesirable, most teams (I have encountered) use usercontrols rather than pages. These are presented via a contentcontrol. You can set content to a usercontrol but most teams use viewmodel first. State that matters is bound and templated from an instance of a viewmodel. To retain state between "navigations" a reference to the viewmodel is retained - which is almost guaranteed to be way lighter on memory than any sort of view.
I am using a base form that has a menu on it so that I don't have to re-do the code for the whole application.
Essentially this form is called StudentBase.cs
Then I have another form called StudentProfile that inherits from StudentBase
public partial class StudentProfile : StudentBase
{
public string selectedPage;
}
This then inherits the menu that is in StudentBase and I don't have to re-do the menu.
On the menu, there are buttons for the individual forms.
So let's say I press on Student Profile I use this to navigate:
private void btnProfile_Click(object sender, EventArgs e)
{
//I don't want the page to reload if it is the current page
if (selectedPage != "Profile")
{
StudentProfile profile = new StudentProfile();
profile.Show();
this.Hide();
}
}
Doing this produces a very laggy result, as well as it looks very glitchy
I override selectedPage in the child forms so in the case of StudentProfile I use:
private void StudentProfile_Load(object sender, EventArgs e)
{
selectedPage = "Profile";
}
I have tested this on my friend's code and his navigation works without lag or glitch. He didn't do the inheritance on the form
The problem with your inheritance solution is that when you create an instance of StudentProfile you also create an instance of the StudentBase form. You show this new instance and hide the old one. You now have two instances of StudentBase (one visible and one hidden). As you open more forms from your menu, you get more instances of StudentBase in memory. Even though they are hidden they still consume resource. This would explain the result you see.
I suggest you do as your friend, which is by the way the typical way child forms are handled from a main menu.
So, I couldn't properly figure out how to use the UserControls. I put it on my to-do list so I can try that at the end of the project if I still have time left.
But, I figured out why it was taking so long to move from one navigation to the other.
I was selecting the student details in the base form using
Student student = new Student();
Student studentDetailsFound = student.GetStudent(2);
I never stopped it from selecting from the database every time it navigates to a new form, thus, the two-second delay every time.
So there were two options to fix this: Static variables or caching.
I used the latter and now it switches to the pages rather fast.
By adding a transition on the form, it made it a lot smoother on the eyes.
Also note: if you are getting the data like I am you should first wait for the designing of the form to finish. So put the GetStudent part into this if:
if (this.Site == null || !this.Site.DesignMode)
{
studentDetailsFound = GetStudent();
}
When my app starts, I so some logic in my AppDelegate and assign the MainPage a page based on the results of that logic.
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init ();
// .....
if(authorizationStatus == PhotoLibraryAuthorizationStatus.Authorized)
{
bokehApp.SetStartupView(serviceLocator.GetService<AlbumsPage>());
}
else
{
bokehApp.SetStartupView(serviceLocator.GetService<StartupPage>());
}
}
In my app.cs, I assign the MainPage the given view from the AppDelegate
public class App : Xamarin.Forms.Application
{
public void SetStartupView(ContentPage page)
{
this.MainPage = new NavigationPage(page);
}
}
In this instance, I am passing StartupPage into the SetStartupView(Page) method. When the user does something, I navigate to the AlbumsPage.
this.Navigation.PushAsync(new AlbumPage());
When I do this, the AlbumPage is created and navigated to; it's Navigation.NavigationStack collection only contains itself, not the Page that it just navigated from. What I want to do, is prevent a call to this.Navigation.PopAsync() navigating back to the StartupPage, which is what currently happens.
Initially I was going to just run a loop and pop the original Page off, and then remove all of the remaining pages, in this case StartupPage.
// Grab the current page, as it is about to become our new Root.
var navigatedPages = this.Navigation.NavigationStack.ToList();
Page newRootPage = navigatedPages.Last();
// Dont include the current item on the stack in the removal.
navigatedPages.Remove(newRootPage);
while(navigatedPages.Count > 0)
{
Page currentPage = navigatedPages.Last();
this.Navigation.RemovePage(currentPage);
}
However, when I look, the Navigation.NavigationStack collection only contains the AlbumsPage. Yet, calling this.Navigation.PopAsync() navigates back to the StartupPage.
What do I need to do in order to reset this navigation stack, so that popping will not navigate back to the initial page?
Update
When I navigate, I've been able to use this:
App.Current.MainPage = new NavigationPage(viewModelPage);
as suggested by #Daniel but this prevents the animation from taking place. I have also tried
await App.Current.MainPage.Navigation.PushAsync(fooPage);
App.Current.MainPage = new NavigationPage(fooPage);
When I do this, I see the new page transitioned to, but once the await call on PushAsync finishes, and the MainPage is replaced, the Page disappears and I'm left with an empty screen.
I really don't want to lose the animations during transitioning from my setup Page to the actual app.
I think what you're looking for is setting:
App.Current.MainPage
to a new NavigationPage. It replaces main page of application.
I was able to solve the issue. This mostly came down to my lack of understanding how the NavigationPage worked. It would seem that each Page is given their own Navigation stack. As I navigated between Pages and examined their NavigationStack collections, they always just had one item in them.
I then started looking at App.Current.MainPage.Navigation and discovered that it actually had the entire stack (both StartupPage and FooPage). I was able to then grab the StartupPage prior to pushing the FooPage onto the navigation stack, and then removing the StartupPage once the navigation to FooPage was completed. This essentially let me reset the root page, while keeping the transition animation between the views.
Page originalRootPage = App.Current.MainPage.Navigation.NavigationStack.Last();
await App.Current.MainPage.Navigation.PushAsync(new FooPage());
App.Current.MainPage.Navigation.RemovePage(originalRootPage);
I'll mark this as answered when the time-period has expired and i'm allowed to.
This is fix my problem.
protected override bool OnBackButtonPressed()
{
foreach (Page page in Navigation.ModalStack)
{
page.Navigation.PopModalAsync();
}
return true;
}
I'm currently building a universal app but I'm concentrating on the WP8.1 part of it right now. I'm using MVVMLight with this project.
For simplicity sake, we'll just assume that I only have 2 pages in the project.
Page1.xaml contains a list which has various items. The Page1.xaml is binded to its own ViewModel i.e. Page1ViewModel. Each item in the list represents a viewModel i.e. ItemViewModel.
When I tap on an item, I call the following code:
public RelayCommand<ItemViewModel> ItemTapCommand
{
get
{
return this._itemTapCommand ?? (this._itemTapCommand =
new RelayCommand<ItemViewModel>((msg) =>
ExecuteItempTapCommand(msg)));
}
}
When an item in the list is tapped, I call the following code:
private object ExecuteItempTapCommand(ItemViewModel selectedItemViewModel)
{
Page2ViewModel page2ViewModel =
SimpleIoc.Default.GetInstance<ItemViewModel>();
page2ViewModel.SelectedItem = selectedItemViewModel;
_navigationService.Navigate(typeof(Page2),
selectedItemViewModel);
return null;
}
As you can see I'm using my Ioc to create get an instance of my Page2ViewModel and I then set the SelectedItem to the selectedItemViewModel.
Once it is set, I navigate to Page2 which is binded to my Page2ViewModel.
What I want to know is, is the above is ok to do? I've seen plenty of examples when dealing with passing object from one page to another is done by passing an Id for example and then I request the information from Page2, but why request it again when most of the information I need is already in my SelectedItemViewModel since it represents the tapped item in my list in Page1.
If it's not correct, what is the best way to go about this using MVVMLight?
Can you provide a sample? I've seen something about Messaging but I'm not sure how this would work as if I navigate to my page2, the Page2ViewModel will only be initiated when the page is created, so how can it receive a message? The way I have it above seems to initiate the Page2ViewModel and my Pag2 loads, it's re-using it and everything bind correctly but I'm not sure this is the correct way to go about it.
Any help would be appreciated.
Thanks.
In your Page2ViewModel, why not use
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Page2SelectedItem = e.Parameter as ItemViewModel;
base.OnNavigatedTo(e);
}
It looks like you are packing that data in with your _navigationService.Navigate call already.
With that set up, what happens if you just change to:
private object ExecuteItempTapCommand(ItemViewModel selectedItemViewModel)
{
_navigationService.Navigate(typeof(Page2), selectedItemViewModel);
return null;
}
You can use the ViewModel to get it if you do some work before that.
Read this blog post by Marco Minerva called Calling ViewModel methods in response to Page navigation events using MVVM Light in WinRT
which explains how to react to OnNavigatedTo and OnNavigatedFrom in the ViewModel.
It's a very cool solution.
In the current released version of WP7 you have no programmatic control over the page stack.
I start at A, go to B, and then C. B is the data entry page for new items, so coming back from C I want to logically land at A. Currently I listen for navigation on B that comes from C and force another back onto A. However, the event doesn't happen soon enough to stop the page from displaying on-screen.
A -> B -> C
C -> A
A is the top level list page. B is the new item page. C is the item detail page.
This only occurs on one form so far so my workaround is to override OnNavigatedTo in the page I want to skip, and call "go back" programmatically. However, this has the undesired effect of briefly showing the page and then immediately navigating off of it.
Is there a workable way to stop the flicker?
Should my workaround instead be to take full control of page navigation, including go backs? This will leave the page stack is a strange state, but that would be hidden from the user if I control all navigation.
I know there is a new feature in Mango to pop a page from the page stack programmatically, but I'm curious to know if there is a solution to the problem in the released version.
Another motivation for this is I don't know what version will be best to target, the latest, or the one that is just enough for the app. For the time being I'm sticking with the live version.
You should read this blog post on solving circular navigation issues and you can download this recipe code which demonstrates non-linear navigation.
I have stopped the flickering by making the root frame transparent for the duration of the skip. This example isn't straight from my code.
Firstly on the page you wish to skip, override OnNavigatedTo and test to see where you have come from (this is where my code gets specific, I keep track of where I am):
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// If I've come from page C, go back again.
NavigationService.GoBack();
}
Secondly, in the main App.xaml.cs register an event handler for Navigating (I put it in public App() constructor):
RootFrame.Navigating += RootFrame_Navigating;
Finally, flesh them out to hide the frame and show it again for the duration of the skip:
private bool _skipped;
private void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
RootFrame.Opacity = 100;
RootFrame.Navigated -= RootFrame_Navigated;
}
private void RootFrame_Navigating(object sender, NavigatingCancelEventArgs e)
{
if (_skipped)
{
_skipped = false;
RootFrame.Navigated += RootFrame_Navigated;
}
if (e.NavigationMode == NavigationMode.Back &&
e.Uri.OriginalString.Contains("ThePage.xaml"))
{
RootFrame.Opacity = 0;
_skipped = true;
}
}
The code to determine when to make the page transparent is also different in my actual code, but I've added an implementation to the answer for illustration. This is almost identical to the code in the recipe linked in another answer, but doesn't involve needing to use a third party DLL.
I provided my own answer because I've seen the sources provided in the other answers before, but never paid attention to the code for hiding the root frame. I am not using the Non-Linear Navigation Service, just the code fragment for frame transparency (I don't need it to detect circular navigation as I'm well aware of the design choices I make in the app and can spot them myself :-)
This suffices as a workaround in the (currently) one case I have where I need to skip a page that doesn't make sense when going back. I'd like to think when Mango comes out I will be best placed targeting the latest version, so this code will soon be defunct.
Sources:
App Hub code recipe for Non-Linear Navigation
Non-Linear Navigation Service Blog Post
There is no way to do it before Mango, except with the flickering way you already know.
In Mango, you can use NavigationService.RemoveBackEntry.
And it's more a question of a bad application design, than anything else. You shouldn't require a immediate page for anything.
Your workaround should be to design a application structure that doesn't require any immediate pages.