Hi there, I'm trying to use code from the video example found here. However that example directly specifies the video source in the xaml, and I'm trying to bind that value. Unfortunately it seems by the time the property on the video control is set from the xml, the binding is not yet available. It seems there is something I'm missing about setup and binding order. Can anyone help me understand what I'm doing wrong here? More detail follows:
This uses a simple View, with a custom control (with an exported renderer), with it's "Source" set to a bound value:
<?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:videoplayer="clr-namespace:BunAndBuneeTV.Controls.VideoPlayer"
x:Class="BunAndBuneeTV.Views.VideoPlayerView">
<videoplayer:VideoPlayer Source="{Binding EpisodePath}" AutoPlay="true"/>
</ContentPage>
The binding is to an equally simple viewmodel:
public class VideoPlayerViewModel :
ViewModelBase
{
private Episode episode;
public string EpisodePath => (episode != null) ? episode.Path : string.Empty;
#region ViewModelBase overrides
public override async Task InitializeAsync(object navigationData)
{
episode = navigationData as Episode;
await base.InitializeAsync(navigationData);
}
#endregion
}
The custom control includes the expected Source property (with a converter):
public class VideoPlayer : View, IVideoPlayerController
{
// Lots of excluded detail..
// Source property
public static readonly BindableProperty SourceProperty =
BindableProperty.Create(nameof(Source), typeof(VideoSource), typeof(VideoPlayer), null);
[TypeConverter(typeof(VideoSourceConverter))]
public VideoSource Source
{
set { SetValue(SourceProperty, value); } // This appears to only be called once, before the BindingContext is set
get { return (VideoSource)GetValue(SourceProperty); }
}
}
And finally the renderer uses Source in OnElementPropertyChanged:
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, UIView>
{
// Lots of excluded detail..
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs args)
{
base.OnElementPropertyChanged(sender, args);
if (args.PropertyName == BnBVideoPlayer.SourceProperty.PropertyName)
{
var source = Element.Source;
// Problem! source is null!
}
}
}
I use a typical NavigationService pattern, so I'll save you the details of that, but fundamentally the order of execution here is:
var page = Activator.CreateInstance(pageType) as Page;
ViewModelBase viewModel = container.Resolve(viewModelType) as ViewModelBase;
page.BindingContext = viewModel;
if (CurrentApplication.MainPage is NavigationPage navigationPage) // This is true
{
await (page.BindingContext as ViewModelBase).InitializeAsync(navigationData);
await navigationPage.PushAsync(page);
}
Now the OnElementPropertyChanged
gets called during the page.BindingContext = viewModel;
, by which time the bindingcontext's InitializeAsync
hasn't been called. I could await the InitializeAsync
before setting the view's BindingContext, but then when the application starts up an exception is thrown because the first page isn't set up yet (because of delay waiting to set it up)-"NSInternalInconsistencyException Reason: Application windows are expected to have a root view controller at the end of application launch". How is this usually resolved? I can't imagine it's unusual having a requirement to wait for bindings to complete on the first page of an application? Does everyone just have a dummy static page that requires no bindings or something?
Thanks in advance