Problem instantiating Tab page Child Viewmodel with Prism NavigationService - xamarin.forms

We have managed to wire a child content page of a tab page to its own viewmodel in our Prism Xamarin Forms app. This works if ContactsScreenViewModel has a parameterless constructor. If we inject the NavigationService into the constructor the code doesn't compile. Does anyone know how we can solve this problem?
Visual Studio is reporting that a parameterless constructor is required
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:TabsTest.Views"
xmlns:viewmodels="clr-namespace:TabsTest.ViewModels"
x:Class="TabsTest.Views.MainPage"
Title="{Binding Title}"
UnselectedTabColor ="Gray"
SelectedTabColor="Green"
BarBackgroundColor="LightGray">
<views:ContactsScreen BackgroundColor="White" Title="Contacts">
<views:ContactsScreen.BindingContext>
<viewmodels:ContactsScreenViewModel/>
</views:ContactsScreen.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Order="Primary"
Priority="1" >
</ToolbarItem>
<ToolbarItem Order="Primary"
Priority="0" >
</ToolbarItem>
</ContentPage.ToolbarItems>
</views:ContactsScreen>
</TabbedPage>
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Text;
namespace TabsTest.ViewModels
{
public class ContactsScreenViewModel
{
private INavigationService _navigationService { get; }
public ContactsScreenViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
}
}

<viewmodels:ContactsScreenViewModel/> is a constructor call... where do you expect the parameters to come from?
You want to either resolve the view model first (through a factory) and then attach the view to it, or make use of the ViewModelLocator to resolve the view model for you (and resolve and inject its dependencies) as described here, for example.
<TabbedPage [...]
xmlns:prism="http://prismlibrary.com"
prism:ViewModelLocator.AutowireViewModel="True">
will resolve an instance of TabbedPageViewModel and put it the BindingContext.
That one should expose an instance of ContactsScreenViewModel through a property:
<views:ContactsScreen BindingContext={Binding ThePropertyOnTabbedPageViewModel}/>
You create that instance to your liking, with all parameters and dependencies you need.

Related

Xamarin.Forms: Display additional tab on specific Platform

Good morning
I have a TabbedPage in my application. Due to restictions I would like to display one more tab on Android than on iOS.
My current TabbedPage look like:
<TabbedPage>
...
<ContentPage x:Name="Page1"/>
<ContentPage x:Name="Page2"/>
<ContentPage x:Name="Page3"/>
<ContentPage x:Name="Page4"/>
</TabbedPage>
I have decided to render Page3 only for Android. I changed my code into:
<TabbedPage>
...
<ContentPage x:Name="Page1"/>
<ContentPage x:Name="Page2"/>
<OnPlatform x:TypeArguments="Page">
<On Platform="Android">
<On.Value>
<ContentPage x:Name="Page3"/>
</On.Value>
</On>
</OnPlatform>
<ContentPage x:Name="Page4"/>
</TabbedPage>
This leads to runtime exception when entering into this TabbedPage:
An error occurred: 'Value cannot be null. Parameter name: item'.
Callstack: ' at
Xamarin.Forms.ObservableWrapper`2[TTrack,TRestrict].Add (TRestrict
item) [0x00008] in D:\a\1\s\Xamarin.Forms.Core\ObservableWrapper.cs:27
I tried to remove x:Name but did not work. Any ideas?
PS. As a workaround I am always able to:
Constructor()
{
if(Runtime.IsIOS)
{
this.Children.Remove(this.Page3)
}
}
However would be better to not render it at all and have it at XAML level.
Unless someone comes up with a way to do this in XAML, this is the best you can do (building on Jason's comment):
xaml:
<ContentPage x:Name="Page1"/>
<ContentPage x:Name="Page2"/>
<!-- no Page3 in XAML -->
<ContentPage x:Name="Page4"/>
c#:
private ContentPage page3;
Constructor()
{
InitializeComponent();
if (Runtime.IsAndroid)
{
page3 = new ContentPage();
// After pages 1 and 2.
Children.Insert(2, page3);
}
}
// Elsewhere in code-behind.
if (page3 != null)
{
...refer to page3...
}
This has the advantage of not constructing the page at all on iOS.
It also makes it easy to test whether page3 is there (page3 != null).
In practice, the other pages will typically be their own classes. Given partial class Page3 : ContentPage elsewhere:
private Page3 page3;
...
page3 = new Page3();
According to this,xaml is an alternative to programming code for instantiating and initializing objects, and organizing those objects in parent-child hierarchies.If you add one additional tab on android it will cause a NullReferenceException on ios.So you may want to do it in codebehind with Device.RuntimePlatform.
Here is my test you can refer to:
switch (Device.RuntimePlatform)
{
case Device.Android:
Children.Add(page1);
Children.Add(page2);
break;
case Device.iOS:
Children.Add(page1);
break;
case Device.UWP:
Children.Add(page1);
break;
}

Xamarin Forms / ReactiveUI - Using ReactiveUI for masterdetail page shows that viewmodel is null

I have a xamarin forms application that is based on ReactiveUI. The viewmodels inherit from ReactiveObject and the codebehind the xaml of the pages , they inherit/are based on from ReactiveContentPage, in case of the masterdetailpage it inherits from the ReactiveMasterDetailPage. The contentpages/masterdetailpage it self are based on ReactiveContent.
What I want to achieve is the following: Get views/xamlpages by giving a viewmodel type. I have the following code for that. But it gives a null at _viewLocator.Resolve...
public async Task<TViewModel> PushViewModelAsync<TViewModel>(bool animated) where TViewModel : class
{
var viewModel = DependencyInjectionService.Get<TViewModel>();
var view = _viewLocator.ResolveView(viewModel);
if (view is Page page)
{
view.ViewModel = viewModel;
await Application.Current.MainPage.Navigation.PushAsync(page, animated);
return viewModel;
}
else
{
throw new ArgumentException($"resolved view for {typeof(TViewModel)} is not a page.");
}
}
The problem is that this works for normal contentpages but it doesnt work for my MasterDetailPage, how is that possible?
<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveMasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:vm="clr-namespace:DriverApp.ViewModels"
x:TypeArguments="vm:MainViewModel"
xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
xmlns:local="clr-namespace:DriverApp.Views; assembly=MasterDetailPageNavigation"
x:Class="DriverApp.Views.MainPage"
Title="Personal Organiser">
<MasterDetailPage.Master>
<local:MasterPage x:Name="masterPage" />
</MasterDetailPage.Master>
<MasterDetailPage.Detail>
<NavigationPage>
<x:Arguments>
<local:PlanningPage/>
</x:Arguments>
</NavigationPage>
</MasterDetailPage.Detail>
The viewmodel:
public class MainViewModel : ReactiveObject, IActivatableViewModel
{
}
I inject it like this:
services.AddTransient<IViewFor<MainViewModel>, MainPage>();
var viewModel = DependencyInjectionService.Get<TViewModel>();
var view = _viewLocator.ResolveView(viewModel);
Based on the code provided I would expect the value you are passing into the _viewLocator.ResolveView to be null. You showed the code where you are registering the IViewFor but I don't see any code registering the ViewModel itself.
Registering IViewFor<Foo>, Foo doesn't register the view model. It tells the type system that a given ViewModel will resolve a specific page.
Also, it seems like you are using a different container than the one provided by ReactiveUI. Which is okay, but you have to make sure all your dependencies are registered correctly in the container you plan to resolve dependencies from.
Lastly. You say it gives a null, but you don't say if the ViewModel you are passing is null or the object you are using to resolve is null.
Either way, I think this is an issue of having the dependencies registered in the correct place, based on the information provided.

Xamarin Forms: How to play Video using Plugin.MediaManager.Forms?

I am trying to play a video using Plugin.MediaManager.Forms and I am referring to this blog.
Step 1: Added Plugin.MediaManager and Plugin.MediaManager.Forms.
Step 2: XAML code - Added a VideoView
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:VideoPlayerApp"
x:Class="VideoPlayerApp.MainPage"
xmlns:forms="clr-namespace:Plugin.MediaManager.Forms;assembly=Plugin.MediaManager.Forms"
Title="Video Player">
<ContentPage.Content>
<StackLayout>
<Label Text="Xamarin Forms"
FontSize="40"
TextColor="Azure"/>
<Label Text="Video Player Application"
FontSize="58"
TextColor="BlueViolet"/>
<Button x:Name="PlayStopButtonText"
Text="Play"
Clicked="PlayStopButton"
TextColor="BlueViolet"/>
<forms:VideoView HeightRequest="202"
WidthRequest="202"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Step 3: xaml.cs code
public partial class MainPage : ContentPage
{
private string videoUrl = "https://sec.ch9.ms/ch9/e68c/690eebb1-797a-40ef-a841-c63dded4e68c/Cognitive-Services-Emotion_high.mp4";
public MainPage()
{
InitializeComponent();
}
private void PlayStopButton(object sender, EventArgs e)
{
if (PlayStopButtonText.Text == "Play")
{
CrossMediaManager.Current.Play(videoUrl, MediaFileType.Video);
PlayStopButtonText.Text = "Stop";
}
else if (PlayStopButtonText.Text == "Stop")
{
CrossMediaManager.Current.Stop();
PlayStopButtonText.Text = "Play";
}
}
}
But getting error on this step:
Error CS0103 The name 'MediaFileType' does not exist in the current context
Step 4: Also added VideoViewRenderer.Init();in MainActivity.cs, AppDelegate.cs and MainPage.xaml.cs. But getting following error for this initialization.
The name 'VideoViewRenderer' does not exist in the current context
Am I missing something? I checked some other blogs but same error occuring. I have added a sample project here.
Android Options Screenshot:
The blog seems out of date. Part of APIs and methods were obsoleted . You should check the newest docs from https://github.com/martijn00/XamarinMediaManager#usage .
use the following code instead of VideoViewRenderer.Init() ;
CrossMediaManager.Current.Init();
And just need to call the method
CrossMediaManager.Current.Play(videoUrl);
And I checked your demo . You need to update the version of Xamarin.Forms to 4.2.x both in share project and specific platforms(Android and iOS) .Which will match to the version of the plugin.
Don't forget to set the Dex complier to d8 .
Right click your Android project -> Property-> Android Options.

AutoWirePartialView with prism does not work or badly used?

I'm trying to use prism 7.1 AutoWirePartialView to bind a PartialView to its viewModel. However, binding is not working, or at least, setting the viewModel to the PartialView does not seem to work, it still has the page's BindingContext as BindingContext.
There is my Page :
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Project.Core.Views.NotConnectedViews.ForecastDemoPage"
xmlns:carouselForecast="clr-namespace:Project.Core.Views.MainViews"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
x:Name="ForecastDemo"
BackgroundColor="{StaticResource PrimaryColorOne}" ControlTemplate="{StaticResource MainAppTemplate}">
<ContentPage.ToolbarItems>
<ToolbarItem Name="SearchForecast" Command="{Binding ShowSearchForecastDemoCommand}" Order="Primary" Icon="ic_search_white_24dp.png" Priority="0" />
</ContentPage.ToolbarItems>
<ContentView x:Name="ContentViewForecast" ControlTemplate="{StaticResource ForecastTownControlTemplate}">
<carouselForecast:ForecastPartialViewCarousel prism:ViewModelLocator.AutowirePartialView="{x:Reference ForecastDemo}"></carouselForecast:ForecastPartialViewCarousel>
</ContentView>
</ContentPage>
Binding: 'DayWeatherForecasts' property not found on
'Project.Core.ViewModels.ForecastDemoPageViewModel', target property:
'Project.Core.Views.MainViews.ForecastPartialViewCarousel.ItemsSource'
As you can see, I'm using the partial view as a ContentPresenter for a ContentView that uses a ControlTemplate.
There is my PartialView :
<carousel:CarouselViewControl x:Name="carouselView"
Position="{Binding CarouselPosition}"
PositionSelectedCommand="{Binding PositionChanged}"
Orientation="Horizontal" AnimateTransition="True" IsSwipeEnabled="False"
ItemsSource="{Binding DayWeatherForecasts}" InterPageSpacing="10"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:carousel="clr-namespace:CarouselView.FormsPlugin.Abstractions;assembly=CarouselView.FormsPlugin.Abstractions"
x:Class="Project.Core.Views.MainViews.ForecastPartialViewCarousel">
<!-- Item template is defined here, removed for readability -->
</carousel:CarouselViewControl>
And this is my PartialView ViewModel :
namespace Project.Core.ViewModels
{
public class ForecastPartialViewCarouselViewModel : ViewModelBase
{
public ForecastPartialViewCarouselViewModel(IForecastService forecastService,
INavigationService navigationService) : base(navigationService)
{
InitStubForecasts();
}
private ObservableCollection<DayWeatherForecast> _dayWeatherForecasts;
public ObservableCollection<DayWeatherForecast> DayWeatherForecasts
{
get => _dayWeatherForecasts;
set => SetProperty(ref _dayWeatherForecasts, value);
}
}
}
Of course DayWeatherForecasts is set with some stub values. I simplified the viewModel for readability purpose.
I'm not using prism AutoWiring viewModel, so in app.xaml.cs :
containerRegistry.RegisterForNavigation<ForecastDemoPage, ForecastDemoPageViewModel>();
Question : Could it be that my PartialViewModel is in the ViewModels folder and that the Partialview I want to be be bound to this ViewModel is under a subfolder MainViews ? Should I create a MainViewsViewModel folder and put my viewModel there ?
EDIT : I tried this solution, but as I expected it does nothing.
If not, then I don't know why it doesnt work ...
Thanks !
Ok so I finally found out that its not enough to put this to my PartialView
prism:ViewModelLocator.AutowirePartialView="{x:Reference ForecastDemo}
As I organized my views in subfolders, prism cannot register alone my ViewModel and my PartialView.
So what I needed is to register manually the ViewModel with the PartialView using ViewModelLocationProvider
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
ViewModelLocationProvider.Register<ForecastPartialViewCarousel,
ForecastPartialViewCarouselViewModel>();
}
It's not only a matter of name, but of namespace too. If I wanted the PartialView to have the correct ViewModel set w/o registering it manually, I should have put my PartialView in the Views root folder and the corresponding ViewModel in the ViewModels root folder (with naming convention)

Back button missing on NavigationPage

I'm using Prism.Forms 6.3 in a Xamarin.Forms 2.3.4.247 project. I'm having a hard time tracking why the back arrow button isn't visible when I navigate to a details page within a Master/Detail setup.
I can navigate to the Pages just fine, but the back-button never shows up. Instead, the hamburger menu icon is always visible. This is my "Master" page.
<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:FloatSink.Apps.Mobile.Views.ValueConverters"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="FloatSink.Apps.Mobile.Views.MainPage"
BackgroundColor="Blue">
<MasterDetailPage.Master>
<ContentPage Title="Menu">
<StackLayout Padding="40">
<Label Text="Hello" />
<Button Text="Feed" Command="{Binding NavigateCommand}" CommandParameter="NavigationPage/FeedPage" />
<Button Text="Options" Command="{Binding NavigateCommand}" CommandParameter="NavigationPage/OptionsPage" />
</StackLayout>
</ContentPage>
</MasterDetailPage.Master>
</MasterDetailPage>
This is two of my Detail pages.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FloatSink.Apps.Mobile.Views.FeedPage">
<Label Text="Hello from Feed Page!" />
</ContentPage>
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FloatSink.Apps.Mobile.Views.OptionsPage">
<Label Text="Hello from Options Page!" />
</ContentPage>
I navigate to it using the CommandParameter specified in my Master page, using the navigation service in the MainPageViewModel.
private void NavigateToPage(string uri)
{
this.navigationService.NavigateAsync(uri);
}
I've setup my NavigationPage like this during the startup of the app so I land on the FeedPage first, then I can navigate to the OptionsPage.
public class App : PrismApplication
{
public App(IPlatformInitializer dependencyRegister) : base(dependencyRegister) { }
protected override void OnInitialized()
{
base.NavigationService.NavigateAsync("MainPage/NavigationPage");
}
protected override void RegisterTypes()
{
var builder = new ContainerBuilder();
builder.RegisterModule<NavigationRegistration>();
builder.RegisterModule<ServicesRegistration>();
base.Container.RegisterTypeForNavigation<NavigationPage>();
// This is deprecated but we have to wait for Prism.Autofac to update itself
builder.Update(base.Container);
}
}
The DI registrations associated with navigation are done in this module:
internal class NavigationRegistration : Module
{
/// <summary>
/// Load the navigation related types into the given builder.
/// </summary>
/// <param name="builder">Container Builder that will be turned into the final IOC Container</param>
protected override void Load(ContainerBuilder builder)
{
// Register the NavigationPage in Xamarin with the Prism Navigation system.
//builder.RegisterType<NavigationPage>().AsSelf();
//PageNavigationRegistry.Register(nameof(NavigationPage), typeof(NavigationPage));
// Get all of the Types that represent a View in our assembly for DI and navigation registration
// If start-up time ever becomes an issue, we can replace this assembly scan with explicit registrations.
Type[] viewTypes = base.ThisAssembly.GetTypes().Where(type => type.IsAssignableTo<Page>()).ToArray();
// Iterate over each discovered View Type and register it with the navigation system and DI container.
for(int index = 0; index < viewTypes.Length; index++)
{
Type viewType = viewTypes[index];
builder.RegisterType(viewType).Named<Page>(viewType.Name);
// If we ever need to disconnect a view name from its Type name, we can do so here.
// We would build a custom attribute to decorate the view with, pull the attribute from the Type
// and register the Type with the attribute value.
PageNavigationRegistry.Register(viewType.Name, viewType);
}
}
}
Again I can each one of my detail pages without problem, the hamburger menu exists and I can open the Master page to view my buttons used for navigating. I just can't navigate backwards once I'm there. Is that something I'm supposed to wire up myself?
I'm not sure I'm reading your question right but it sounds like this is normal behavior. In my (short) experience with XF/Prism, every navigation from the master is a beginning of the stack. If you were to add another page, e.g. from Master->PageA->PageB, I would expect Page A to have the hamburger menu but going to PageB would give you the back arrow.
For using NavigationPage inside uri you should register it for navigation in the App.xaml.cs:
protected override void RegisterTypes()
{
// your registrations
Container.RegisterTypeForNavigation<NavigationPage>();
}
I most cases it is the reason.
To Navigate To Master Page
"/MasterPage/NavigationPage/ViewA"
To Navigate out of Master Page from ViewA and with back button
"ViewB"
You need to start your app with MainPage = new NavigationPage(new StartPage()); That is how it is solved in normal situation. So maybe try to register your MainPage wrapped in a NavigationPage.

Resources