Skip to content

Commit

Permalink
Merge pull request #97 from EdCharbeneau/fixes-dispose-errors
Browse files Browse the repository at this point in the history
Fixes dispose errors caused by lifecycle.
  • Loading branch information
EdCharbeneau authored Aug 30, 2023
2 parents 07668df + b18ce7c commit b3fc614
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 31 deletions.
23 changes: 15 additions & 8 deletions BlazorPro.BlazorSize.BlazorRepl/BlazorReplMediaQueryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public BlazorReplMediaQueryService(IJSRuntime jsRuntime)

// JavaScript uses this value for tracking MediaQueryList instances.
private DotNetObjectReference<MediaQueryList> DotNetInstance { get; set; } = null!;
private readonly List<MediaQueryCache> mediaQueries = new List<MediaQueryCache>();
private readonly List<MediaQueryCache> mediaQueries = new();
public List<MediaQueryCache> MediaQueries => mediaQueries;

private MediaQueryCache? GetMediaQueryFromCache(string byMedia) => mediaQueries?.Find(q => q.MediaRequested == byMedia);
Expand All @@ -36,24 +36,29 @@ public void AddQuery(MediaQuery newMediaQuery)
};
mediaQueries.Add(cache);
}
cache.MediaQueries.Add(newMediaQuery);
cache.MediaQueries?.Add(newMediaQuery);
}

public async Task RemoveQuery(MediaQuery mediaQuery)
{
if (mediaQuery == null) return;

var cache = GetMediaQueryFromCache(byMedia: mediaQuery.Media);
MediaQueryCache? cache = GetMediaQueryFromCache(byMedia: mediaQuery.Media);

if (cache == null) return;
if (cache == null || cache.MediaQueries == null) return;

cache.MediaQueries.Remove(mediaQuery);
if (cache.MediaQueries.Any())
try
{
cache.MediaQueries.Remove(mediaQuery);
if (cache.MediaQueries.Any()) return;
mediaQueries.Remove(cache);
var module = await moduleTask.Value;
IJSObjectReference module = await moduleTask.Value;
await module.InvokeVoidAsync($"removeMediaQuery", DotNetInstance, mediaQuery.InternalMedia.Media);
}
catch (Exception)
{
//https://stackoverflow.com/questions/72488563/blazor-server-side-application-throwing-system-invalidoperationexception-javas
}
}

public async Task Initialize(MediaQuery mediaQuery)
Expand All @@ -75,6 +80,7 @@ public async Task Initialize(MediaQuery mediaQuery)
cache.Value = await task;
cache.Loading = task.IsCompleted;
// When loading is complete dispatch an update to all subscribers.
if (cache.MediaQueries is null) return;
foreach (var item in cache.MediaQueries)
{
item.MediaQueryChanged(cache.Value);
Expand All @@ -100,11 +106,12 @@ public async Task CreateMediaQueryList(DotNetObjectReference<MediaQueryList> dot

public async ValueTask DisposeAsync()
{
if (DotNetInstance != null)
if (DotNetInstance is not null)
{
var module = await moduleTask.Value;
await module.InvokeVoidAsync("removeMediaQueryList", DotNetInstance);
DotNetInstance.Dispose();
GC.SuppressFinalize(this);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion BlazorSize/BlazorPro.BlazorSize.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<Version>6.2.1</Version>
<Version>6.2.2</Version>
<Authors>Ed Charbeneau</Authors>
<Company>EdCh1rbeneau.com</Company>
<Description>A JavaScript interop for Blazor used to detect the browser's screen size and perform Media Query tests. BlazorSize uses the DOM API `matchMedia` to test screen size. BlazorSize was created to allow Blazor components to render adaptively.</Description>
Expand All @@ -16,6 +16,7 @@
<RepositoryUrl>https://github.com/EdCharbeneau/BlazorSize</RepositoryUrl>
<PackageTags>Blazor, JavaScript Interop</PackageTags>
<PackageReleaseNotes>
6.2.2 Fixes more codepaths that may result in Microsoft.JSInterop.JSDisconnectedException
6.2.1 Fixed Microsoft.JSInterop.JSDisconnectedException
6.2.0 Fixed bug where MediaQuery dispose was not always working correctly.
6.1.2 Fixed casing on JS modules, fixes deployment issues on Linux. (regression)
Expand Down
3 changes: 2 additions & 1 deletion BlazorSize/MediaQuery/MediaQueryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public class MediaQueryCache
/// <summary>
/// Media Queries that share a RequestedMedia value. Used to aggregate event handlers and minimize JS calls.
/// </summary>
public List<MediaQuery> MediaQueries { get; set; } = new List<MediaQuery>();
// Nullable because Blazor's lifecycle sometimes GC's this object before the cleanup routines are finished.
public List<MediaQuery>? MediaQueries { get; set; } = new List<MediaQuery>();
}
}

1 change: 1 addition & 0 deletions BlazorSize/MediaQuery/MediaQueryList.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public void MediaQueryChanged(MediaQueryArgs args)
if (cache is null) return;

// Dispatch events to all subscribers
if (cache.MediaQueries is null) return;
foreach (var item in cache.MediaQueries)
{
item.MediaQueryChanged(args);
Expand Down
49 changes: 28 additions & 21 deletions BlazorSize/MediaQuery/MediaQueryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,49 +18,55 @@ public MediaQueryService(IJSRuntime jsRuntime)

// JavaScript uses this value for tracking MediaQueryList instances.
private DotNetObjectReference<MediaQueryList> DotNetInstance { get; set; } = null!;
private readonly List<MediaQueryCache> mediaQueries = new List<MediaQueryCache>();
private readonly List<MediaQueryCache> mediaQueries = new();
public List<MediaQueryCache> MediaQueries => mediaQueries;

private MediaQueryCache? GetMediaQueryFromCache(string byMedia) => mediaQueries?.Find(q => q.MediaRequested == byMedia);

public void AddQuery(MediaQuery newMediaQuery)
{
var cache = GetMediaQueryFromCache(byMedia: newMediaQuery.Media);
if (cache == null)
MediaQueryCache? cache = GetMediaQueryFromCache(byMedia: newMediaQuery.Media);
if (cache is null)
{
cache = new MediaQueryCache
{
MediaRequested = newMediaQuery.Media,
};
mediaQueries.Add(cache);
}
cache.MediaQueries.Add(newMediaQuery);
cache.MediaQueries?.Add(newMediaQuery);
}

public async Task RemoveQuery(MediaQuery mediaQuery)
{
if (mediaQuery == null) return;
if (mediaQuery is null) return;

var cache = GetMediaQueryFromCache(byMedia: mediaQuery.Media);
MediaQueryCache? cache = GetMediaQueryFromCache(byMedia: mediaQuery.Media);

if (cache == null) return;
if (cache is null || cache.MediaQueries is null) return;

cache.MediaQueries.Remove(mediaQuery);
if (!cache.MediaQueries.Any())
try
{
cache.MediaQueries.Remove(mediaQuery);
if (cache.MediaQueries.Any()) return;
mediaQueries.Remove(cache);
var module = await moduleTask.Value;
IJSObjectReference module = await moduleTask.Value;
await module.InvokeVoidAsync($"removeMediaQuery", DotNetInstance, mediaQuery.InternalMedia.Media);
}
catch (Exception)
{
//https://github.com/EdCharbeneau/BlazorSize/issues/93
//https://stackoverflow.com/questions/72488563/blazor-server-side-application-throwing-system-invalidoperationexception-javas
}
}

public async Task Initialize(MediaQuery mediaQuery)
{
if (mediaQuery?.Media == null) return;
if (mediaQuery?.Media is null) return;
var cache = GetMediaQueryFromCache(byMedia: mediaQuery.Media);
if (cache == null) return;
if (cache is null) return;

if (cache.Value == null)
if (cache.Value is null)
{
// If we don't flag the cache as loading, duplicate requests will be sent async.
// Duplicate requests = poor performance, esp with web sockets.
Expand All @@ -73,6 +79,7 @@ public async Task Initialize(MediaQuery mediaQuery)
cache.Value = await task;
cache.Loading = task.IsCompleted;
// When loading is complete dispatch an update to all subscribers.
cache.MediaQueries ??= new List<MediaQuery>();
foreach (var item in cache.MediaQueries)
{
item.MediaQueryChanged(cache.Value);
Expand Down Expand Up @@ -100,17 +107,17 @@ public async ValueTask DisposeAsync()
{
try
{
if (DotNetInstance != null)
{
var module = await moduleTask.Value;
await module.InvokeVoidAsync("removeMediaQueryList", DotNetInstance);
DotNetInstance.Dispose();
await module.DisposeAsync();
GC.SuppressFinalize(this);
}
if (DotNetInstance is null) return;

var module = await moduleTask.Value;
await module.InvokeVoidAsync("removeMediaQueryList", DotNetInstance);
DotNetInstance.Dispose();
await module.DisposeAsync();
GC.SuppressFinalize(this);
}
catch (Exception)
{
//https://github.com/EdCharbeneau/BlazorSize/issues/93
//https://stackoverflow.com/questions/72488563/blazor-server-side-application-throwing-system-invalidoperationexception-javas
}
}
Expand Down

0 comments on commit b3fc614

Please sign in to comment.