Bound property not yet set when xaml tag requires it

MattBenicMattBenic ZAMember ✭✭

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 :smile:

Answers

  • ShantimohanElchuriShantimohanElchuri USMember ✭✭✭✭✭

    @MattBenic Was the code in the last segment in your post from the sample that you referred? Why the page.BindingContext is cast as ViewModelBase? I may be doing as follows in the VideoPlayerView.xaml.cs:

    public class VideoPlayerView
    {
        public ViewPlayerViewModel viewPlayerViewModel;
    
        public VideoPlayerView()
        {
            InitializeCompnent();
    
            viewPlayerViewModel = new ViewPlayerViewModel();
            viewPlayerViewModel.InitializeAsync(navigationData).Wait();
            this.BindingContext = viewPlayerViewModel;
        }
    }
    

    I have not seen your referred sample code. So you will have to adapt this to your code requirements.

    I am using Wait() since async class constructors don't work.

Sign In or Register to comment.