beginner
beginner

Reputation: 97

Blazor StateHasChanged() does not work after switching pages (async)

StateHasChanged() works normally when working without changing pages. However, if you go back to another page and do the same thing again, it doesn't work. The value in the variable enters normally, so if you do an event (click the button), the desired output is made again. I think StateHasChanged() doesn't know the change. Could this be due to async?

<div>
  @foreach (var noti in PushNotifications.notifications)
  {
    <p>@noti</p>
  }
</div>

@code {
...
  async public void subscribe()
  {
    AppContext.SetSwitch(
         "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport",
         true);
    SocketsHttpHandler handler = new SocketsHttpHandler();

    var httpClient = new HttpClient(handler)
      {
        DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact,
        DefaultRequestVersion = new Version(2, 0),
      };

    using var channel = GrpcChannel.ForAddress(IPADDRESS, new GrpcChannelOptions { HttpClient = httpClient });


    var client = new PushNotificationService.PushNotificationServiceClient(channel);

    SubscriptionRequest subscriptionRequest = new SubscriptionRequest { RegistrationId = PushNotifications.registration_id };
    subscriptionRequest.Topics.Add(createTopics);

    var reply = client.subscribe(subscriptionRequest);
    try
    {
      await foreach (var subscriptionResponse in reply.ResponseStream.ReadAllAsync())
      {

        PushNotifications.notifications.Add(subscriptionResponse);
        
        //this.StateHasChanged();
        await InvokeAsync(StateHasChanged);
        await Task.Delay(1);
      }
    }
...
namespace GrpcStreamClient.Data
{
  public class PushNotifications
  {
    public string registration_id { get; set; } = string.Empty;
    public bool[] isChecked { get; set; } = new bool[3];
    public List<String> notifications { get; set; } = new List<string>();
  }
}

Upvotes: 1

Views: 1476

Answers (1)

Ibrahim Timimi
Ibrahim Timimi

Reputation: 3750

Since I can't run your subscribe functions as it requires dependencies and other logic, I can recommend you use an event-based solution for updating notifications on the UI whenever a new one is added. Every page that listens to the event will be able to render the new notifications.

As for your grpc logic, you need to fix your code. Provide a function for subscribe and an event listener for incoming messages, that way when you have a new message you can invoke NoticationsChanged action implemented in the code below.

Follow the steps below to implement a Singleton service with a page that listens for updated notications. It should provide you a better structure to implement your solution.

// program.cs
builder.Services.AddSingleton<IMyService, MyService>();

Interface:

public interface IMyService
{
    PushNotifications pushNotifications { get; set; }
    event Action NotificationsChanged;
    void AddNotification();
    void Subscribe();
}

Service:

public class MyService: IMyService
{
    public PushNotifications pushNotifications { get; set; }
    public event Action NotificationsChanged;

    public MyService()
    {
        pushNotifications = new PushNotifications();
        Subscribe();
    }

    public void AddNotification()
    {
        var faker = new Faker("en"); // using Bogus;
        var notification = faker.Lorem.Sentence(10);

        pushNotifications.notifications.Add(notification);
        NotificationsChanged?.Invoke(); // invoke action when you add notification
    }

    async public void Subscribe()
    {
        //
    }
}

In your Subscribe function where you add notification, you can invoke the action like this:

pushNotifications.notifications.Add(subscriptionResponse);
NotificationsChanged?.Invoke(); // invoke action when you add notification
await Task.Delay(100);

NotificationPage1.razor: (change route and title for page 2)

@page "/"
@inject IMyService myService
@implements IDisposable

<PageTitle>Index</PageTitle>
<h1>Page 1</h1>

<ul>
    @foreach (var notification in myService.pushNotifications.notifications)
    {
        <li>@notification</li>
    }
</ul>

<button class="btn btn-primary"
        @onclick="@(() => myService.AddNotification())">
    Add Notification
</button>


@code {

    protected override void OnInitialized()
    {
        myService.NotificationsChanged += OnNotify;
    }

    public void Dispose()
    {
        myService.NotificationsChanged -= OnNotify;
    }

    private async void OnNotify()
    {
        try
        {
            await InvokeAsync(() => StateHasChanged())
            .ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            // log exception
        }
    }
}

Output:

Output

Upvotes: 2

Related Questions