2

I'm working on a cross-platform project using Blazor WebAssembly and .NET MAUI Blazor Hybrid (targeting .NET 8). I'm using Fluxor for state management across both environments.

In my shared component library, I have a base class called SettingsBase that looks something like this:

public class SettingsBase : ComponentBase
{
    protected void OnStateChanged(object? sender, EventArgs e)
    {
        StateHasChanged();
    }
}

All of my Razor components inherit from SettingsBase. In each component, I register for state change notifications like this:

ApplicationState.StateChanged += OnStateChanged;

The idea is that whenever any relevant state changes, the OnStateChanged method triggers StateHasChanged(), causing the component to re-render — and this works perfectly in Blazor WebAssembly.

However, when I run the same shared components in a .NET MAUI Blazor Hybrid app, I get the following exception:

System.NullReferenceException
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()

It seems like StateHasChanged() is being called before the component is fully initialized or attached to the Blazor rendering system in MAUI.

My questions: Has anyone else encountered this issue when using StateHasChanged() in shared components between Blazor WASM and MAUI?

Does MAUI Blazor Hybrid handle component lifecycle differently in a way that affects StateHasChanged()?

Is there a best practice for subscribing to state changes in shared Razor components that avoids this issue?

4
  • Is IDisposable implemented on your base component, and unattach the event handler. Commented Apr 6 at 7:30
  • Yes, my "SettingsBase.cs" class implements both "ComponentBase" and "IDisposable". All event handlers are properly detached when the component is disposed. Commented Apr 7 at 1:34
  • What platform did you test on? And did you debug to check which line caused this error? Did you mean the ApplicationState.StateChanged invoked before the component initialized? Commented Apr 8 at 8:35
  • The platform I used is MAUI (Windows Machine). The error is thrown exactly at the line where StateHasChanged() is called. I believe this happens because ApplicationState is being accessed before the component has fully initialized. I've since identified the cause of the problem and will provide an answer below. Commented Apr 25 at 8:00

2 Answers 2

1

In our MAUI Blazor we have reactivity done quite similarly. We use Fody for PropertyChanged and for subscribing EverCodo.ChangesMonitoring .

Has anyone else encountered this issue when using StateHasChanged() in shared components between Blazor WASM and MAUI?

I think we experienced something similar when we were putting together the abstract layer with subscription functionality

Does MAUI Blazor Hybrid handle component lifecycle differently in a way that affects StateHasChanged()?

The only diff I am aware of is it can call event handers from different thread and StateHasChanged will crash. Thats why we use await InvokeAsync(StateHasChanged); The InvokeAsync runs the code on UI thread

Is there a best practice for subscribing to state changes in shared Razor components that avoids this issue?

Recently we did the following. Our page base class implements IDisposable All pages inherit from this base (in your case SettingsBase) we have there:

public override async Task SetParametersAsync(ParameterView parameters)
{
    parameters.SetParameterProperties(this);

    if(!_isSubscribed)
    {
        //TODO - Here do the event subscription
            _isSubscribed = true;
    }

    //after calling this - component rendering starts + OnInitialized is called
    await base.SetParametersAsync(ParameterView.Empty);
}

public override void Dispose()
{
    //unsubscribe all your events

    base.Dispose();
}

The component itself subscribes to the Rerendering event when it is ready and the null exception shouldnt occur anymore

Sign up to request clarification or add additional context in comments.

3 Comments

Hi, thank you for sharing your implementation. My SettingsBase.cs class implements both ComponentBase and IDisposable. All event handlers are properly detached at the component level when it is disposed. I also tried using await InvokeAsync(StateHasChanged) as you suggested, but the issue persists. I have a feeling this might be related to something unusual in my MAUI implementation or configuration.
@LouisNong question is, where do you register those events ? In what stage of component initialization ? Because it really matters if it is in SetParametersAsync or in constructor, OnParametersSet, OnInitialized etc.
I initially registered the event in OnInitializedAsync. I also tried registering it in SetParametersAsync, following the pattern you suggested. However, the issue still persisted. I've since identified the cause of the problem and will provide an answer below.
1

Okay, so I found the answer to my own question. But before diving into the solution, I want to share a bit about how I implemented Fluxor in my project.

According to the Fluxor documentation, Fluxor is typically registered like this:

var currentAssembly = typeof(Program).Assembly;
builder.Services.AddFluxor(options => options.ScanAssemblies(currentAssembly));

In my implementation, I wanted to abstract my ApplicationState behind an interface (IApplicationState). So I did the following:

builder.Services
    .AddFluxor(o =>
    {
        o.ScanAssemblies(
            typeof(IApplicationState).Assembly,
            typeof(ApplicationState).Assembly
        );
    })
    .AddSingleton<IApplicationState>(sp => sp.GetRequiredService<ApplicationState>());

Notice that I'm using IApplicationState instead of referencing ApplicationState directly in my components or other services.

This setup works perfectly fine in Blazor WebAssembly. However, for some reason (which I still haven't fully figured out), MAUI Blazor Hybrid doesn't play well with this pattern.

When I removed the interface and registered the state directly like this:

builder.Services
    .AddFluxor(o =>
    {
        o.ScanAssemblies(
            typeof(ApplicationState).Assembly
        );
    });

…it started working correctly in MAUI Blazor Hybrid.

So in short: using an interface for your state class seems to cause issues in MAUI Blazor Hybrid, even though it works fine in Blazor WASM.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.