Prabhav Mehra
Prabhav Mehra

Reputation: 168

How to raise event from platform specific code and handle it in main source code in .NET MAUI

I have an interface called “IAppConfig”, that has an EventHandler PolicyDefaultChanged with custom EventAgrs. The interface is inherited in Platform specific code(iOS in this case) and I raise the event. The "PolicyDefaultChanged" Event is null and hence the .Invoke() is not being called. That is done because of the null check using "PolicyDefaultChanged?". I have handled the event in main code as shown below.

// in Common Code / Controls /PolicyDefaultChangedEventArgs 
public class PolicyDefaultChangedEventArgs
    : EventArgs {
    public Dictionary<string, object> DefaultPolicy { get; private set; }

    public PolicyDefaultChangedEventArgs(
        Dictionary<string, object> defaultPolicy) {
        Console.WriteLine("Policy Changed");
        DefaultPolicy = defaultPolicy;
    }
}
// Event Handler in Interface
// in Common Code / IAppConfig.cs
public interface IAppConfig {
    public event EventHandler<Controls.PolicyDefaultChangedEventArgs>
        PolicyDefaultChanged;
}
// iOS specific code
// in Platforms / iOS /AppConfigService.cs 
public class AppConfigService : IAppConfig {
    private Dictionary<string, object> m_dict =
        new Dictionary<string, object>();

    public event EventHandler<Controls.PolicyDefaultChangedEventArgs>
        PolicyDefaultChanged;

    public AppConfigService() {
        Dictionary<string, object> dict = new Dictionary<string, object>();
        dict["keyString"] = "test";
        setAppConfigurationSettings(dict);
    }

    public void setAppConfigurationSettings(Dictionary<string, object> dict) {
        m_dict = dict;

        Controls.PolicyDefaultChangedEventArgs ars =
            new Controls.PolicyDefaultChangedEventArgs(dict);
        OnPolicyDefaultChanged(ars);
    }

    protected virtual void OnPolicyDefaultChanged(
        Controls.PolicyDefaultChangedEventArgs e) {
        PolicyDefaultChanged?.Invoke(
            this, e);  // line which doesn't get raise event due to "?" check
                       // which shows that PolicyDefaultChanged is null
    }
}
// Main Code
// in MainPage.xaml.cs

public partial class MainPage : ContentPage {
    public MainPage(IAppConfig appConfig) {
        InitializeComponent();

        appConfig.PolicyDefaultChanged += OnPolicyDefaultsChanged;
    }
    private void OnPolicyDefaultsChanged(
        object sender, Controls.PolicyDefaultChangedEventArgs e) {
        Debug.WriteLine("Policy changed");
    }
}
// MAUIProgram.cs

public static class MauiProgram {
    public static MauiApp CreateMauiApp() {
        var builder = MauiApp.CreateBuilder();
        builder.UseMauiApp<App>()
            .ConfigureFonts(fonts => {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            })

#if DEBUG
                builder.Logging.AddDebug();
#endif

        builder.Services.AddTransient<IAppConfig, AppConfigService>();
        builder.Services.AddTransient<MainPage>();

        return builder.Build();
    }
}

What am I doing wrong. Added code sample below The function in MainCode is not being called/ event not being raised

EDIT 1:

Okay I got it to work using PropertyMapper but I don’t think it is the right way because I am using this IView class which I want to avoid. Please find the sample code below (which works). I would want to use it without PropertyMapper or without calling this custom “View” in my main class. The current code mean that I have to declare this view in my MainPage.xaml and then use that to connect to the handler instead of the event handler declared in the IAppConfig/the interface

// Common Code Controls/PolicyDefault.cs
public interface IHybridPolicyDefault : IView {
    event EventHandler<Controls.PolicyDefaultChangedEventArgs>
        PolicyDefaultChanged;

    void Refresh();

    Dictionary<string, object> DefaultPolicy { get; set; }

    void InvokePolicyDict(Dictionary<string, object> data);
}

// Common Code Controls/PolicyDefault.cs
public class MyClass : View, IHybridPolicyDefault {
    public string Error {
        get => throw new NotImplementedException();
        set => throw new NotImplementedException();
    }
    public Dictionary<string, object> DefaultPolicy {
        get => (Dictionary<string, object>)GetValue(SourceProperty);
        set => SetValue(SourceProperty, value);
    }

    public event EventHandler<PolicyDefaultChangedEventArgs>
        PolicyDefaultChanged;

    public static BindableProperty SourceProperty =
        BindableProperty.Create(propertyName: "DefaultPolicy",
                                returnType: typeof(Dictionary<string, object>),
                                declaringType: typeof(MyClass),
                                defaultValue: new Dictionary<string, object>(),
                                propertyChanged: OnPolicyDefaultChanged);

    protected static void OnPolicyDefaultChanged(BindableObject bindable,
                                                 object oldValue,
                                                 object newValue) {
        // PolicyDefaultChanged.Invoke(this, e);

        Console.WriteLine("Source invoke");
        var view = bindable as MyClass;
        Console.WriteLine("Test" + oldValue + " " + newValue);

        bindable.Dispatcher.Dispatch(() => {
            view.PolicyDefaultChanged.Invoke(
                view, new Controls.PolicyDefaultChangedEventArgs(
                          newValue as Dictionary<string, object>));
        });
    }

    public void InvokePolicyDict(Dictionary<string, object> data) {
        PolicyDefaultChanged?.Invoke(this,
                                     new PolicyDefaultChangedEventArgs(data));
    }

    public void Refresh() {
        throw new NotImplementedException();
    }
}
// iOS Specific Code: Platforms/iOS/AppConfigService.cs
public class AppConfigService
    : ViewHandler<Controls.IHybridPolicyDefault, UIKit.UIView>,
      IAppConfig {
    public static PropertyMapper<Controls.IHybridPolicyDefault,
                                 AppConfigService> HybridWebViewMapper =
        new PropertyMapper<Controls.IHybridPolicyDefault, AppConfigService>(
            ViewHandler.ViewMapper);

    public event EventHandler<Controls.PolicyDefaultChangedEventArgs>
        PolicyDefaultChanged;

    public AppConfigService() : base(HybridWebViewMapper) {}

    private void VirtualView_SourceChanged(
        object sender, Controls.PolicyDefaultChangedEventArgs e) {
        Console.WriteLine("Policy Changed handler");
    }

    protected override void ConnectHandler(UIView platformView) {
        base.ConnectHandler(platformView);
        if (VirtualView.DefaultPolicy != null) {
            m_instance = this;
            Dictionary<string, object> dict = new Dictionary<string, object>();
            dict["keyString"] = "test";
            setAppConfigurationSettings(dict);
        }
        VirtualView.PolicyDefaultChanged += VirtualView_SourceChanged;
    }

    public void setAppConfigurationSettings(Dictionary<string, object> dict) {
        m_dict = dict;

        VirtualView?.InvokePolicyDict(dict);
    }

    protected override void DisconnectHandler(UIView platformView) {
        platformView.Dispose();
        base.DisconnectHandler(platformView);
    }
}
// MainPage.xaml
<ContentPage xmlns = "http://schemas.microsoft.com/dotnet/2021/maui" xmlns : x =
     "http://schemas.microsoft.com/winfx/2009/xaml" x : Class =
         "ManagedAppConfig.MainPage" xmlns : control =
             "clr-namespace:ManagedAppConfig.Controls">

    <ScrollView><VerticalStackLayout Spacing = "25" Padding =
                     "30,0" VerticalOptions = "Center">
    <control : MyClass x : Name = "test" />

    <Image Source = "dotnet_bot.png" SemanticProperties.Description =
         "Cute dot net bot waving hi to you!" HeightRequest =
             "200" HorizontalOptions = "Center" />

    <Label Text = "Hello, World!" SemanticProperties.HeadingLevel =
         "Level1" FontSize = "32" HorizontalOptions = "Center" />

    <Label Text = "Welcome to .NET Multi-platform App UI" SemanticProperties
                      .HeadingLevel = "Level2" SemanticProperties.Description =
         "Welcome to dot net Multi platform App U I" FontSize =
             "18" HorizontalOptions = "Center" />

    <Button x : Name = "CounterBtn" Text = "Click me" SemanticProperties.Hint =
         "Counts the number of times you click" Clicked =
             "OnCounterClicked" HorizontalOptions = "Center" />

    </VerticalStackLayout></ScrollView>
</ContentPage>
// MainPage.xaml.cs
public partial class MainPage : ContentPage {
    public MainPage(IAppConfig appConfig) {
        InitializeComponent();

        test.PolicyDefaultChanged += OnPolicyDefaultsChanged;
    }

    private void OnPolicyDefaultsChanged(
        object sender, Controls.PolicyDefaultChangedEventArgs e) {
        // This works
        Console.WriteLine(e.DefaultPolicy.Count);
    }
}

Upvotes: 0

Views: 941

Answers (1)

Batesias
Batesias

Reputation: 2146

In your first code sample, your event is not raised because it's invoked from the constructor of AppConfigService, which is before the code of MainPage can even subscribe to that event.

The sequence goes like this:

  • MainPage()
    • AppConfigService()

      PolicyDefaultChanged has no subscribers.

      • setAppConfigurationSettings()

        PolicyDefaultChanged is not raised because it still has no subscribers.

    • appConfig.PolicyDefaultChanged += OnPolicyDefaultsChanged;

      PolicyDefaultChanged is now subscribed to, but it's too late.

Upvotes: 1

Related Questions