Xamarin.Forms binding not updating after initial value

13,661

Solution 1

Answer

You need to add RaisePropertyChanged in BuggyTitle property declaration.

ViewModel

namespace MyProject
{
    [ImplementPropertyChanged]
    public class BuggyViewModel : MvxViewModel
    {
        private Random _random;

        string  _BuggyTitle { get; set; }

        public string BuggyTitle
        {
            get { return _BuggyTitle; }
            set { _BuggyTitle = value; RaisePropertyChanged(() => BuggyTitle); }
        }

        public BuggyViewModel()
        {
            _random = new Random();
        }

        public override void Start()
        {
            base.Start();
            BuggyTitle = "" + _random.Next(1000);
        }
    }
}

-----New Update------

Code behind code

var binding_context = (BindingContext as BuggyViewModel);
if (binding_context != null)
{
    Device.BeginInvokeOnMainThread(() =>
    {
        BuggyLabel.Text = binding_context.BuggyLabelText;
    });
}

Solution 2

I don't have any experience at all with Xamarin (but i do want to try it out in the future when i get as comfortable as possible with UWP), but i guess the Data Binding process should be working similar to what i am used to there ...

You are mentioning that you have no problem with the values that are set when the page first loads, however when you actually update the values there's no "linking" to the visual layer, despite at debug time you actually seeing the value being set to something completely different from it's initial state. Since you are dealing with properties-only viewmodel (Collections for instance in UWP are another level of events which need to be exposed), RaisePropertyChanged seems like the correct choice.

What i cannot understand is if when you first create your page, the Binding which you are creating is at least specified as One-Way mode, so changes in your viewmodel properties are propagated onto your UI when their set accessor methods are called.

You are setting your page context to viewmodel (each i figure is the same as DataContext in UWP/WPF), and therefore you can actually access those properties with the {Binding } markup. But what is the default mode for this operation in Xamarin ? (in UWP it is actually OneWay, and therefore it would work right of the bat for this situation ...). I have seen that in Xamarin it might be a bit different , since you also have the Default option. Can that be it?

PS. Hopefully this might be useful to you, despite my lack of experience with Xamarin.

Edit2 Implementing the INotifyPropertyChanged,

   public class BuggyViewModel : MvxViewModel, INotifyPropertyChanged
   {
        public event PropertyChangedEventHandler PropertyChanged;
        private Random _random;

        string  _BuggyTitle { get; set; }

        public string BuggyTitle
        {
            get { return _BuggyTitle; }
            set { _BuggyTitle = value; RaisePropertyChanged(() => 
                   BuggyTitle); }
        }

        public BuggyViewModel()
        {
            _random = new Random();
        }

        public override void Start()
        {
            base.Start();
            BuggyTitle = "" + _random.Next(1000);
        }


        protected void OnPropertyChanged(string propertyName)
        {
           var handler = PropertyChanged;
           if (handler != null)
              handler(this, new PropertyChangedEventArgs(propertyName));
        }
   }
Share:
13,661
Ash
Author by

Ash

Updated on June 13, 2022

Comments

  • Ash
    Ash almost 2 years

    I'm binding the title of my Xamarin.Forms.ContentPage to a property BuggyTitle in my view model (VM). The VM derives from MvxViewModel. Here's the simplified version:

    BuggyPage.xaml:

    <?xml version="1.0" encoding="UTF-8"?>
    <local:ContentPage Title="{Binding BuggyTitle}"
                xmlns="http://xamarin.com/schemas/2014/forms" 
                xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
                x:Class="MyProject.BuggyPage"
                xmlns:local="clr-namespace:Xamarin.Forms;assembly=MyProject">
    <ContentPage.Content NavigationPage.HasNavigationBar="false">
            <Grid>
                <ScrollView>
                    <!--and so on-->
    </ContentPage.Content>
    </local:ContentPage>
    

    BuggyViewModel.cs:

    namespace MyProject
    {
        [ImplementPropertyChanged]
        public class BuggyViewModel : MvxViewModel
        {
          private Random _random;
    
          public string BuggyTitle {get; set;}
    
          public BuggyViewModel()
          {
              _random = new Random();
          }
    
          public override void Start()
            {
                base.Start();
                BuggyTitle = "" + _random.Next(1000);
                RaisePropertyChanged("BuggyTitle"); // this seems to make no difference
            }
        }
    }
    

    There's not much going on in the code behind other than a call to InitializeComponent() in the constructor.

    The page is mapped to the VM generically in my project (not actually 'my' project, it's existing design), and it boils down to these (again, simplified) lines of code:

    public static Page CreatePage(MvxViewModelRequest request)
    {
        var viewModelName = request.ViewModelType.Name;
        var pageName = viewModelName.Replace ("ViewModel", "Page");
        var pageType = (typeof (MvxPagePresentationHelpers)).GetTypeInfo ().Assembly.CreatableTypes().FirstOrDefault(t => t.Name == pageName);
        var viewModelLoader = Mvx.Resolve<IMvxViewModelLoader>();
        var viewModel = viewModelLoader.LoadViewModel(request, null);
        var page = Activator.CreateInstance(pageType) as Page;
        page.BindingContext = viewModel;
    
       return page;
    }
    

    The problem:

    When BuggyPage loads, I initially get the correct value for the title. Whenever it is displayed after that, even though I can see in the debugger that BuggyTitle is getting updated correctly, the change does not appear in the page.

    Question:

    Why don't updates to BuggyTitle get reflected in the page?

    Edit 1:

    To further describe the weirdness, I added a Label to my ContentPage, with x:Name="BuggyLabel" and Text="{Binding BuggyLabelText}". In my code-behind, I added this:

    var binding_context = (BindingContext as BuggyViewModel);
    if (binding_context != null)
    {
        BuggyLabel.Text = binding_context.BuggyLabelText;
    }
    

    I set a breakpoint at BuggyLabel.Text =. It gets hit every time the page loads, and BuggyLabel.Text already seems to have the correct value (i.e, whatever binding_context.BuggyLabelText is set to). However, the actual page displayed only ever shows what the text in this label is initially set to.

    And yes, have clean/built about a million times.

    Edit 2 (further weirdness):

    I put this in the code-behind so that it runs during page load:

    var binding_context = (BindingContext as BuggyViewModel);
    if (binding_context != null)
    {
        Device.BeginInvokeOnMainThread(() =>
        {
            binding_context.RefreshTitleCommand.Execute(null);
        });
    }
    

    This again changes values in the debugger, but these changes don't get reflected in the displayed page.

    I then added a button to the page and bound it to RefreshTitleCommand, and wham! the page updates its display.

    Unfortunately I can't use this. Not only is it incredibly hackish, I can't have the user pressing buttons to have the page display what it's meant to on load.

    I wonder if there's some caching going on with MvvmCross or Xamarin.

    • Martijn00
      Martijn00 over 6 years
      Does BuggyTitle have a public field to get and set the property in?
    • Ash
      Ash over 6 years
      Yes, sorry, forgot to add it in the question. I've edited the question.
    • Pavan V Parekh
      Pavan V Parekh over 6 years
      @Ash, Remove code-behind code. Not needed it
    • Ash
      Ash over 6 years
      Not the point I was trying to make. Yes I know I don't need it. Was trying to show how weird things are. The binding clearly is working, but the view simply refuses to display updates.
    • Ivan Ičin
      Ivan Ičin over 6 years
      as you have local:ContentPage, it probably does something wrong, but you haven't shown the code for that.
    • Ash
      Ash over 6 years
      What code do I need to show? ContentPage belongs in the Xamarin.Forms namespace.
    • Emil
      Emil about 6 years
      does your binding work if you remove the code behind initial value? I guess, it does I have the same problem with the visibility for a very specific reason, i must use both binding and initial value in the code behind but when I have code behind binding doesnt work. Have you ever found a solution for this?
    • Ash
      Ash about 6 years
      No it doesn't work. Tried every possible permutation, nothing works. I never managed to solve this, so rather just went around the problem.
  • Ash
    Ash over 6 years
    Tried this also, followed by a clean/build. Still no luck :/ Have a look at the edit that I've made. I know that the binding is working fine because in the debugger I can see that the label's text property is always set to the correct value. However, it just doesn't reflect on the actual displayed page. Tried this on iOS simulator as well as physical device. Same issue on both.
  • Pavan V Parekh
    Pavan V Parekh over 6 years
    @Ash I have update code behind code. Check it and let me know
  • Ash
    Ash over 6 years
    Ooh, can't wait to try this; seems promising. Anything main-thread-ish generally always seems to make UI things magically work. Will keep you posted.
  • Ash
    Ash over 6 years
    The binding direction isn't the issue. Changes from VM are propagating through to the View properties fine, just not displayed.
  • André B
    André B over 6 years
    Event if at debug time, by inspecting your property binding in the View you see the property with the updated value, i don't think you can actually conclude that the view is actually binded to that value.
  • Ash
    Ash over 6 years
    So how does it update in the display when I press a button that is bound to a command in the VM that updates the VM property in question? Ref: Edit 2.
  • André B
    André B over 6 years
    It might actually still have the previous property's value, if the binding mode is actually not set properly, or you are not raising the event properly (the event should clearly identify the property which has changed).
  • André B
    André B over 6 years
    @Ash If the binding is not working, it is normal that after you set your property to a new value you are going to see it displayed in the console, but not on the view.
  • André B
    André B over 6 years
    That means that much likely something is wrong between your VM and the View connection. Just to get this out of the way, have you tried this: Text="{Binding BuggyLabelText, Mode=OneWay}" ?
  • Ash
    Ash over 6 years
    I'll put that in explicitly and get back.
  • Ash
    Ash over 6 years
    Nope. Didn't work. I've got this line that I'm examining in the debugger: RaisePropertyChanged(() => BuggyTitle);. In the debugger, BuggyTitle has the correct (updated) value, but the page displayed once again shows the old (initial) value.
  • André B
    André B over 6 years
    Instead of using the RaisePropertyChanged, try to check out the INotifyPropertyChanged Interface and have your class inherit from it. This class exposes the public event PropertyChangedEventHandler PropertyChanged, which notifies that certain property has changed.
  • Ash
    Ash over 6 years
    Well I'm using MvvmCross which means I'm restricted to its navigation system. So I can't just implement INotifyPropertyChanged, and do things the usual way. Instead, I've tried implementing IMvxNotifyPropertyChanged, and RaisePropertyChanged is a helper as per the MvvmCross docs which itself makes use of PropertyChanged.
  • Pavan V Parekh
    Pavan V Parekh over 6 years
    @Ash, add BuggyLabel.Text = binding_context.BuggyLabelText; in BeginInvokeOnMainThread method
  • Ash
    Ash over 6 years
    That's what I tried before I included Edit 2, and it didn't work.
  • Pavan V Parekh
    Pavan V Parekh over 6 years
    @Ash, I think your ViewModel is not bonded with View. Check it with the set value of BuggyLabelText in declaring like public string BuggyTitle{get;set;}="Test"; and let me know.
  • Ash
    Ash over 6 years
    If it's not bound, how do initial values appear fine? And how do values update fine when they are triggered by a user event such as a button click?