Search code examples
xamarin.formsmvvmviewmodelinotifypropertychangedpropertychanged

Xamarin forms PropertyChanged object is null


So I have looked through many of the answers already posted in StackOverflow, and I cannot seem to find one that applies.

First, let me explain my Xamarin Forms App. I have the traditional solution structure (.Net Standard 2.0 class library with the Andoid and iOS application projects). The application I am working on is part of a suite of application which share a large amount of code (header, footer, error handling etc) via an additional .Net 2.0 standard class library. In this class I have a viewmodel for exception handling as well as a standard header content view used in all applications that are part of suite.

Within the header I show an error icon in the header area that the user can click on to report the errors etc. when the exception processor adds an exception. I bind the IsVisible property of the image control for the error icon to a property in the exception handling viewmodel. Every time my a page displays (all pages use the shared header), the get for the property in the viewmodelthe IsVisible property bound to is called. So it would appear the binding is configured correctly.

The problem is that when I add an error via my exception handling view model and then attempt to notify the UI that the bound property has changed, the PropertyChange event objet - defined as follows:

public event PropertyChangedEventHandler PropertyChanged;

is always null. Thus the 'get' for the bound property is never called and my UI is not updated. I hope this is something simple but I have not been able to figure it out.

PLEASE NOTE!! A different solution in the suite (with UWP app stub only) which uses the exact same the exact same contentviews and code, works exactly as expected. In other words when the exception processor adds an error, the bound property is interrogated and the error icon displays as expected.

Below is my code. Any assistance would be greatly appreciated.

XAML CODE FOR APPHEADER CONTENT VIEW

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:sfGrad ="clr-namespace:Syncfusion.XForms.Graphics;assembly=Syncfusion.Core.XForms"
             BackgroundColor="#4D648D"
             x:Class="FCISharedAll.AppHeader">
    <ContentView.Content>
        <StackLayout x:Name="slMaster" HorizontalOptions="Fill" VerticalOptions="Start" Orientation="Vertical" Margin="0,0,0,-6" 
                     Spacing="0">
            <Grid x:Name="grdGradient" HorizontalOptions="Fill" VerticalOptions="Start" HeightRequest="110" MinimumHeightRequest="110">
                <sfGrad:SfGradientView>
                    <sfGrad:SfGradientView.BackgroundBrush>
                        <sfGrad:SfLinearGradientBrush StartPoint="0.5, 0" EndPoint="0.5, 1">
                            <sfGrad:SfLinearGradientBrush.GradientStops>
                                <sfGrad:SfGradientStop Color="#4D648D" Offset="0.0" />
                                <sfGrad:SfGradientStop Color="#283655" Offset="0.5" />
                                <sfGrad:SfGradientStop Color="#4D648D" Offset="1.0" />
                            </sfGrad:SfLinearGradientBrush.GradientStops>
                        </sfGrad:SfLinearGradientBrush>
                    </sfGrad:SfGradientView.BackgroundBrush>
                </sfGrad:SfGradientView>
                <Grid x:Name="grdHeader" HorizontalOptions="Fill" VerticalOptions="Start">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Image x:Name="imgHeader" Source="fciapp.png" HeightRequest="110" MinimumHeightRequest="110" HorizontalOptions="Fill"
                           VerticalOptions="Center"  Aspect="AspectFit" Grid.Column="0" />
                    <Image x:Name="imgError" Source="error.png" HorizontalOptions="End" VerticalOptions="Center" Margin="8,8,8,8"
                       IsVisible="{Binding ShowErrorIcon}" Grid.Column="0">
                        <Image.GestureRecognizers>
                            <TapGestureRecognizer Tapped="imgError_Tapped" />
                        </Image.GestureRecognizers>
                    </Image>
                </Grid>
            </Grid>
        </StackLayout>
    </ContentView.Content>
</ContentView>

C# CODE FOR APPHEADER CONTENT VIEW

  using Rg.Plugins.Popup.Services;
    using System;
    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;
    
    namespace FCISharedAll
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class AppHeader : ContentView
        {
    
            public AppHeader()
            {
                InitializeComponent();
                NavigationPage.SetHasNavigationBar(this, false);
                imgError.BindingContext = FCISharedAllHelperClass.gvm_GlobalExceptionClass;
            }
    
            private async void imgError_Tapped(object sender, EventArgs e)
            {
                try
                {
                    await PopupNavigation.Instance.PushAsync(new FCISharedAll.ExceptionDisplay(FCISharedAllHelperClass.gvm_GlobalExceptionClass));
                }
                catch (Exception ex)
                {
                    SharedErrorHandler.ProcessException(ex);
                }
            }
        }
    }

C# CODE FOR EXCEPTION HANDLER VIEWMODEL

namespace FCISharedAll
{
    public class CompleteException
    {
        public DateTime Error_DateTime { get; set; }
        public Exception Error { get; set; }
    }

    public class ExceptionProcessor : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private List<CompleteException> iobj_ApplicationExceptions { get; set; }

        public ExceptionProcessor()
        {
            try
            {
                if (iobj_ApplicationExceptions == null)
                {
                    iobj_ApplicationExceptions = new List<CompleteException>();
                }
            }
            catch (Exception ex)
            {
                iobj_ApplicationExceptions.Add(new CompleteException() { Error_DateTime = DateTime.Now, Error = ex });
            }
        }

        public bool ShowErrorIcon
        {
            get
            {
                return (iobj_ApplicationExceptions == null || iobj_ApplicationExceptions.Count > 0);
            }
        }
        public void AddExceptionToList(Exception pobj_Exception)
        {
            try
            {
                iobj_ApplicationExceptions.Add(new CompleteException() { Error_DateTime = DateTime.Now, Error = pobj_Exception });
                //NotifyPropertyChanged(ge_PreferenceKey.ShowErrorIcon.ToString());
                NotifyPropertyChanged("ShowErrorIcon");
            }
            catch (Exception ex)
            {
                iobj_ApplicationExceptions.Add(new CompleteException() { Error_DateTime = DateTime.Now, Error = ex });
            }
        }

        public int ErrorCount
        {
            get
            {
                int li_ReturnValue = 0;
                if (iobj_ApplicationExceptions != null)
                {
                    li_ReturnValue = iobj_ApplicationExceptions.Count;
                }
                return li_ReturnValue;
            }
        }

        public void RemoveProcessedExceptions(int pi_NumberOfExceptionsToInclude)
        {
            int li_ErrorsToProcess = 0;
            try
            {
                li_ErrorsToProcess = (pi_NumberOfExceptionsToInclude <= iobj_ApplicationExceptions.Count ? pi_NumberOfExceptionsToInclude : iobj_ApplicationExceptions.Count);

                for (int li_IX = 0; li_IX <= li_ErrorsToProcess - 1; li_IX++)
                {
                    iobj_ApplicationExceptions.RemoveAt(0);
                }

                
                NotifyPropertyChanged(ge_PreferenceKey.ErrorCount.ToString());
                NotifyPropertyChanged(ge_PreferenceKey.ShowErrorIcon.ToString());
            }
            catch (Exception ex)
            {
                iobj_ApplicationExceptions.Add(new CompleteException() { Error_DateTime = DateTime.Now, Error = ex });
            }
        }

        public string PrepareErrorText(int pi_NumberOfExceptionsToInclude = 5)
        {
            string ls_EmailText = "";
            int li_ErrorsToProcess = 0;

            try
            {
                li_ErrorsToProcess = (pi_NumberOfExceptionsToInclude <= iobj_ApplicationExceptions.Count ? pi_NumberOfExceptionsToInclude : iobj_ApplicationExceptions.Count);

                for (int li_IX = 0; li_IX <= li_ErrorsToProcess - 1; li_IX++)
                {
                    ls_EmailText += FormatException(iobj_ApplicationExceptions[li_IX]) + System.Environment.NewLine;
                }

            }
            catch (Exception ex)
            {
                iobj_ApplicationExceptions.Add(new CompleteException() { Error_DateTime = DateTime.Now, Error = ex });
            }

            return ls_EmailText;
        }

        private string FormatException(CompleteException pobj_CompleteException)
        {
            string ls_ReturnText = "";
            ls_ReturnText = "Date: " + pobj_CompleteException.Error_DateTime + System.Environment.NewLine +
                "Message: " + (pobj_CompleteException.Error.Message == null ? "" : pobj_CompleteException.Error.Message + System.Environment.NewLine) +
                "Source: " + (pobj_CompleteException.Error.Source == null ? "" : pobj_CompleteException.Error.Source + System.Environment.NewLine) +
                "Stack Trace: " + (pobj_CompleteException.Error.StackTrace == null ? "" : pobj_CompleteException.Error.StackTrace + System.Environment.NewLine);

            if (pobj_CompleteException.Error.InnerException != null)
            {
                ls_ReturnText += "Inner Exception Message: " + (pobj_CompleteException.Error.InnerException.Message == null ? "" : pobj_CompleteException.Error.InnerException.Message + System.Environment.NewLine) +
                    "Inner Exception Source: " + (pobj_CompleteException.Error.InnerException.Source == null ? "" : pobj_CompleteException.Error.InnerException.Source + System.Environment.NewLine) +
                    "Inner Exception Stack Trace: " + (pobj_CompleteException.Error.InnerException.StackTrace == null ? "" : pobj_CompleteException.Error.InnerException.StackTrace);
            }

            if (ls_ReturnText.LastIndexOf(System.Environment.NewLine) > ls_ReturnText.Length - 5)
            {
                //Remove the last line linefeed
                ls_ReturnText = ls_ReturnText.Substring(0, ls_ReturnText.LastIndexOf(System.Environment.NewLine));
            }

            return ls_ReturnText;
        }

        /// <summary>
        /// Manual Notification to subscribers that a property has changed. 
        /// </summary>
        private void NotifyPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (null != handler)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Here is the code for the helper class in my Shared Code project that instantiates the Exception Processing viewmodel as a static object

using FCISharedAll.FCICommObjects;
using System;

namespace FCISharedAll
{
    public static class FCISharedAllHelperClass
    {
        public static ExceptionProcessor gvm_GlobalExceptionClass = new ExceptionProcessor();
        public static ThemeConfig gobj_ThemeConfig { get; set; }

        public static bool IsCheckInPage { get; set; } = false;

        //internal
        private static KioskInfo iobj_KioskInfo = null;
        public static KioskInfo HostKioskInfo
        {
            get
            {
                if (iobj_KioskInfo == null)
                {
                    iobj_KioskInfo = new KioskInfo();
                }
                return iobj_KioskInfo;
            }
            set
            {
                iobj_KioskInfo = value;
            }

        }

        public static void ConfigureTheme(bool pb_UseKioskTheme)
        {
            try
            {
                gobj_ThemeConfig = new ThemeConfig(pb_UseKioskTheme);
            }
            catch (Exception ex)
            {
                SharedErrorHandler.ProcessException(ex);
            }
        }
    }

}

I know this a lot to look through but I would appreciate any assistance that can be provided.

UPDATE!!! ARG - you know after you post the question you think you figure it out. I believe I was actually getting two different instances of my exception handler class, one from the Xamarin forms .net class library and one within my shared code. I am removing the view model from my xamarin forms .net class library to see if this fixes the problem and I will update.


Solution

  • SO - in my case what had happened was I had an instance of the viewmodel defined in my Xamarin Forms project and a static version of the object defined in my shared code. I forgot to remove the instance of the viewmodel from the Xamarin Forms app and thus everything in that project referencing the viewmodel was accessing a different instance than all the code in my shared code class. Once I removed the instance in my Xamarin Forms app and pointed everything to the static instance in my separate shared code project, everything worked fine. Maybe this will help someone else.