Refreshing data in the UI inside the Load method after setting it for the first time

Hello @Team,

I'm implementing an access page. This page contains a datalist with accesses that are constantly refreshed from the database. Each access has a list of entities. Each entity has a main photo that is stored locally in the server machine.

To implement this I ended up with this algorithm inside the Load method:

  1. Get accesses from the database
  2. Do some controller requests to get the main photos
  3. Wait for some pre-configured time
  4. Execute the Load method again

Here's the needed code to understand this problem:

protected async System.Threading.Tasks.Task Load() {

    [...]

    accesses = await Global.Services.DatabaseService.ReadList<Global.Models.Access>("orderby=access.id DESC", $"limit={int.Parse(widget?.settings?.FirstOrDefault(x => x.property == "count")?.value ?? "0")}", "accessPoint=true", "accessEntities=true", "person=true", "personPhoto=true", "vehicle=true", "identifier=true", "group=true");

    if (accesses != null)
        foreach (var mainPhoto in accesses.SelectMany(x => x.accessEntities).Select(x => x.person).Where(x => x.personPhotos.Any(y => y.main)).Select(x => x.personPhotos.FirstOrDefault()))
            mainPhoto.photo = await Photo.Download<Global.Models.PersonPhoto>(mainPhoto.photoId);

    await Task.Delay(int.Parse(widget?.settings?.FirstOrDefault(x => x.property == "refreshRate")?.value ?? "0"));

    await Load();
}

My problem is that the access data in the UI never have the photo value. If I remove the last two awaits in the method, they will be set as they should, but it won't automatically refresh.

Here's a list of things that I tried:

  • Call Reload() before the Task.Delay
  • Set accesses to null and then set to its old value
  • Change accesses from IEnumerable<Global.Models.Access> to ObservableCollection<Global.Models.Access>
  • Change Task.Delay to Thread.Sleep, ManualResetEvent or Timer
  • Manually calling all the accesses setter code before the Task.Delay

I think you have created an endless loop by calling the Load method recursively. This won't work. You can instead create a timer which invokes the Load method at a predefined interval and calls StateHasChanged at the end.

1 Like

I'm back to this because when using your recommended method, the timer will never stop, as it's never disposed. If I leave the page, it will keep running and thus creating multiple instances every time the page loads. Any workarounds?

My coding is not in the .razor page, but in the designer.cs file.

UPDATE: here's a code snapshot:

The breakpoint will be constantly hit after leaving the page. Even closing the browser window won't stop this event from triggering.

UPDATE2: after some research, there seems to be some differences between System.Timers.Timer and System.Threading.Timer, but the code below produces the same error:

System.Threading.Timer timer = new System.Threading.Timer(x => accessDataList.Reload(), null, 0, int.Parse(widget?.settings?.FirstOrDefault(x => x.property == "refreshRate")?.value ?? "0"))

Maybe there's a way I could hook the timer.Dispose to the page.Dispose, if it happens at all?

Hi @kim,

Yes, you need to stop and dispose the timer otherwise it will leak. You can implement IDisposable in your page:

public partial class EditProductComponent : IDisposable
{
    public void Dispose()
    {
      timer.Stop();
      timer.Dispose();
    }
}
2 Likes

Yes! Thank you so much!