Xamarin Forms Navigation inside Frame - xamarin.forms

Coming from UWP's development, i wondering is it is possible to have navigation inside a Frame in Xamarin Forms. I saw on documentation Frame element have INavigation property, so i tried this code :
MyFrame.Navigation.PushAsync(new Page1());
But when i'm trying to execute this code on Android, i get the following error :
System.InvalidOperationException: 'PushAsync is not supported globally
on Android, please use a NavigationPage.'
But when i do this :
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
The navigation does not work as expected because it is global and not inside the Frame. We have to have a global navigation or it is impossible to have a specific navigation ? The goal is to have a static part in the app and a part where navigation takes place.
For example, with Uno Platform we can use UWP Frame and perform navigation inside it, so i wondering why it is not possible in Xamarin Forms.

We could define a property with type of INavigation in a custom Frame
public class MyFrame:Frame
{
public INavigation CurrentNavigation { get; private set; }
public MyFrame (INavigation navigation)
{
CurrentNavigation = navigation;
}
}
in MainPage
public MainPage()
{
InitializeComponent();
MyFrame myFrame = new MyFrame(this.Navigation);
myFrame.CurrentNavigation.PushAsync(new Page1());
}

Related

How to create viewmodels instance when app start time in xamarin forms MVVM

My aim is to access bindable property across the the App. But My current framework ViewModel Instance create multiple time
My Requirement : I have the cart count in the bottomTray(CheckuoutViewModel) i want to increase the cart count any where in the app page, but in this cart count not update when back click, its only working on forward navigation, the reason behind CheckoutViewModel instance create each and every time. so that i'm try to instant creation at earlier.
Here I'm list out sample ViewModel and calling method
Login ViewModel
Checkuout ViewModel(This view model common for all page)
BaseNavigationViewModel(Its BaseViewModel)
As of now i'm calling when BindinContext each and every time like,
new LoginViewMode(navigation)
new CheckoutViewModel(navigation)
what will do to create all ViewModel instance when app start time like ViewModel Locator?
Im tried
public static ViewModelLocator Locator
{
get { return locator ?? (locator = new ViewModelLocator()); }
}
And ViewModel Locator
public ViewModelLocator()
{
navigation = App.Current.MainPage.Navigation;
}
internal CustomTabBarViewModel CustomTabBarVM
{
get
{
return customTabBarVM ?? (customTabBarVM = new CustomTabBarViewModel(navigation));
}
}
And CustomTabBar.xaml.cs
public CustomTabBar()
{
viewModel = App.Locator.CustomTabBarVM;
InitializeComponent();
BindingContext = viewModel;
}
and Expectation
App.Locator.CustomTabBarVM.BadgeCartCount = OrderObject.Instance.ORDER_OBJECT.Items.Count;
This approach is working fine but it's create some navigation issues
A singleton instance is a common feature of virtually all MVVM frameworks (Prism, FreshMVVM etc). If you aren't using a framework (if you aren't, I would STRONGLY advise you consider using one), below is a solution.
To obtain a single instance of a ViewModel you can use the App class to host the object and access it whenever you need.
Create a public static property of your ViewModel:
public static MyViewModel MyViewModelInstance { get; }
Create an instance in the constructor of the app
public App()
{
InitializeComponent();
MyViewModelInstance = new MyViewModel();
var myPage = new MyPage()
{
BindingContext = MyViewModelInstance
};
var navPage = new NavigationPage(myPage);
MainPage = navPage;
}
Whenever you create a new page, access the shared instance
// This method is just an example of how you might create a new page and wire up the view model
async void GoNextClicked(System.Object sender, System.EventArgs e)
{
var myPage = new MyPage()
{
BindingContext = App.MyViewModelInstance
};
await this.Navigation.PushAsync(myPage);
}
This approach comes with a few caveats, you are creating instances when the app loads not when they are needed (Eagerly loading). So a performance optimisation would be to use Lazy<T> to handle the creation of these objects. However this is logic that has already been written for you in MVVM frameworks, they are there to help you and you should be using them.
Lazy Load
You can save memory and performance at startup by lazy loading the viewmodel, here is this example rewritten to support this pattern:
public static MyViewModel MyViewModelInstance
{
get => _myViewModelInstanceFactory.Value;
}
private static Lazy<MyViewModel> _myViewModelInstanceFactory = new Lazy<MyViewModel>(() => new MyViewModel(), true);
public App()
{
InitializeComponent();
var myPage = new MyPage()
{
BindingContext = MyViewModelInstance
};
var navPage = new NavigationPage(myPage);
MainPage = navPage;
}
Now this object won't be created until it is accessed by your code, and once it has been accessed once it has already been created and will go on to live in memory for the rest of your apps lifecycle.
Axemasta has good answer about re-use of a shared view model instance.
I'll give an alternative approach to the underlying need given in one comment: how to have a static property (so the value is common), and Bind to it when Binding to an instance.
Use this approach if you do want a different CheckoutViewModel for each new page. For example, if there are other properties that should be set up differently, depending on the page.
public class CheckoutViewModel : : INotifyPropertyChanged // or your MVVM library's base class for ViewModels.
{
public static int SharedCount { get; set; }
public void IncrementCount()
{
Count = Count + 1;
}
public int Count {
get => SharedCount;
set {
// Exact code might be simpler if using an MVVM library.
if (SharedCount != value)
{
SharedCount = value;
OnPropertyChanged("Count");
}
}
}
}
}
LIMITATION: This assumes that only the current instance of CheckoutViewModel is visible; if you need to "notify" OTHER Views (update other CheckoutViewModel instances), then you'll need a "publish/subscribe" solution. In Xamarin Forms, one such solution is MessagingCenter.

Using Absolute Navigation is not resetting the stack and is forcing modal navigation

I have an application with a Loginscreen, and a Mainpage
When I initialize my application I set the Navigation to "NavigationPage/LoginPage". When I log into the application I reset the stack with absolute navigation using the route "/NavigationPage/MainPage". All of my pages have a label bound to the current navigation Uri and when I reset the stack after logging in and navigate to the mainpage my Uri looks like this
/NavigationPage/LoginPage/MainPage?useModalNavigation=true
I've read that if you have duplicate pages on top of one another this can cause the navigation to become modal by default. But I'm resetting the stack and even resetting the NavigationPage. This doesn't cause any immediate issues, but I have found that in deeper parts of my application's navigation stack, some navigation isn't working and this seems to be the cause
Does anyone know why this is happening?
My code
App.xaml.cs
protected override async void OnInitialized()
{
InitializeComponent();
await NavigationService.NavigateAsync("NavigationPage/LoginPage");
}
LoginPageViewModel.cs
public ICommand LoginCommand { get; }
public LoginPageViewModel(INavigationService navigationService) : base(navigationService)
{
LoginCommand = new Command(Login);
NavUri = NavigationService.GetNavigationUriPath();
}
private async void Login()
{
await NavigationService.NavigateAsync("/NavigationPage/MainPage");
}
MainPageViewModel.cs
public MainPageViewModel(INavigationService navigationService) : base(navigationService)
{
Title = "Main Page";
NavUri = NavigationService.GetNavigationUriPath(); // current URI on the mainpage is /NavigationPage/LoginPage/MainPage?useModalNavigation=true
NavigateToPageACommand = new Command(NavigateToPageA);
}
I'm using;
Xamarin Forms 4.6.0.726
Prism.Dryloc 7.2.0.1422
Appears to be just a temporary value that occurs during OnNavigatingFrom. While the stack is being reset the Navigation appears to be Modal, however it is actually not. Discovered this by adding INavigationAware to my ViewModelBase and viewing the result of GetNavigationUriPath in the OnNavigatedTo and OnNavigatingFrom Methods.

Keeping screen turned on for certain pages

I am using a portable project so do not have direct access to native code.
I have an interface in my project that allows me to access native objects in the Android/iOS projects. We use this primarily for playing audio.
Android, for example, has things like
Window w = new Window();
w.SetFlags(WindowManagerFlags.Fullscreen, WindowManagerFlags.KeepScreenOn);
However the main issue would be accessing a Window object. I could pass a Xamarin.Forms.Page object to the native code, but there would be no way (I don't think) to cast it to a native Android Window object to access the flags.
Is there a way to do this with a portable project?
You can't do this without platform specific services or renderers. A portable project will have to call platform specific code in order to achieve this.
From that platform specific code, either as a DependencyService or Renderer, you can access the Window object through the Forms.Context. The Forms.Context is your Android Activity, through which you can reach the Window object.
On Android it works like this:
Android.Views.Window window = (Forms.Context as Activity).Window;
window.SetFlags(WindowManagerFlags.KeepScreenOn);
On iOS you can try this (Apple docs):
UIApplication.SharedApplication.IdleTimerDisabled = true;
Now there is a plugin doing exactly what Tim wrote
https://learn.microsoft.com/en-us/xamarin/essentials/screen-lock
simple source code is here
https://github.com/xamarin/Essentials/blob/main/Samples/Samples/ViewModel/KeepScreenOnViewModel.cs
using System.Windows.Input;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Samples.ViewModel
{
public class KeepScreenOnViewModel : BaseViewModel
{
public KeepScreenOnViewModel()
{
RequestActiveCommand = new Command(OnRequestActive);
RequestReleaseCommand = new Command(OnRequestRelease);
}
public bool IsActive => DeviceDisplay.KeepScreenOn;
public ICommand RequestActiveCommand { get; }
public ICommand RequestReleaseCommand { get; }
void OnRequestActive()
{
DeviceDisplay.KeepScreenOn = true;
OnPropertyChanged(nameof(IsActive));
}
void OnRequestRelease()
{
DeviceDisplay.KeepScreenOn = false;
OnPropertyChanged(nameof(IsActive));
}
}
}
For Xamarin Forms Android.
Renders file I included below code
Window window = (Forms.Context as Activity).Window;
window.AddFlags(WindowManagerFlags.KeepScreenOn);

open new window and pass collection to viewmodel using mvvm

I have a view which consists of a button when i click i want to open window in which i want to pass observable collection to viewmodel of the new window open.I am using below code it is working but i am not sure it is mvvm pattern or not.
ViewCode:
NewWindow newWindowDialog;
newWindowDialog = new NewWindow()
{
newWindowDialogCollection = suppliersList,
Owner = Application.Current.MainWindow
};
newWindowDialog.ShowDialog();
NewWindow Dialog Code:
public partial class NewWindow : Window
{
public NewWindow()
{
InitializeComponent();
newWindowDialogCollection = new ObservableCollection<SModel>();
DataContext = this;
}
public ObservableCollection<ISupplierModel> newWindowDialogCollection { get; set; }
}
In xaml "newWindowDialogCollection" act as my data source for binding
But i am not sure above way is right way to impliment to open new window and pass collection. and i want to do it by pure mvvm & viewmodel.
Please let me know your thoughts
Creating a new window in a viewmodel couples the View and the ViewModel layers tightly, which defeats the purpose of MVVM.
Also, you shouldn't have business data in the View, but in a ViewModel instead, even with dialogs.
What you'll see some MVVM frameworks do is implementing service classes for everything that is not (easily) manageable by either the Model, the View or the ViewModel. Displaying dialogs is one of those things.
I'll use the example of Catel to demonstrate.
Catel offers a IUIVisualizerService interface, that you can inject in the constructor of your viewmodel:
public MyViewModel(IUIVisualizerService visualizerService)
{
this._visualizerService = visualizerService;
}
And to open a new dialog, since Catel internally matches views and viewmodels, you simple create the dialog's viewmodel and resolve the appropriate view. This way, you can pass you data to the viewmodel's constructor as you please:
var viewModel = new MyViewModel(suppliersList);
_visualizerService.Show(viewModel);
You could create a window service that is responsible for open the window and then inject your view model with such a service. You then call the service's ShowWindow method to open the window from your view model, e.g.:
Service:
public interface IWindowService
{
void ShowWindow(ObservableCollection<string> collection);
}
public class WindowService : IWindowService
{
public void ShowWindow(ObservableCollection<string> collection);
{
NewWindow newWindowDialog = new NewWindow()
{
newWindowDialogCollection = collection,
Owner = Application.Current.MainWindow
};
newWindowDialog.ShowDialog();
}
}
View Model:
public class ViewModel
{
private readonly IWindowService _service;
public ViewModel(IWindowService service)
{
_service = service;
}
//...
public void OpenCommandExecuted()
{
_service.ShowWindow(_theCollectionToPass);
}
}
Using this approach the view model only knows about an interface that you can easily unit test, without actually opening up a window, by providing a mock implementation of the interface.

Caliburn.Micro for WPF Controls Project

My current situation is that I have a winforms application which I would like to create a new WPF controls library projects to start showing new screens in WPF. I would really like to leverage the Caliburn.Micro framework but im struggling getting it set up. I currently have my bootstrapper as a Singleton so that I can initialize it and also Im setting the useApplication property in the base contructor to false. I'm not sure where to go from here any help would be appreciated. thanks
public class AppBootstrapper : BootstrapperBase
{
private static AppBootstrapper bootsrapper;
public static void InitializeInstance()
{
if (bootsrapper == null)
{
bootsrapper = new AppBootstrapper();
}
}
public AppBootstrapper() : base(false)
{
Initialize();
}
}

Resources