Skip to content

Commit d1825b6

Browse files
Add Support for C# Hot Reload (#232)
* Add CommunityToolkitMetadataUpdateHandler * Include `Type[]` in `ReloadApplication` event * Add `HotReloadHandler` * `dotnet format` * Change `Type[]` -> `IReadOnlyList<Type>` * Update HotReloadHandler.cs * Update HotReloadHandler.cs * Update CommunityToolkit.Maui.Markup.csproj * Update CommunityToolkitMetadataUpdateHandler.cs * Update samples/CommunityToolkit.Maui.Markup.Sample/HotReloadHandler.cs Co-authored-by: Vladislav Antonyuk <[email protected]> * Add `ICommunityToolkitHotReloadHandler.cs` * Add `Window` support * Replace `#if DEBUG` with `[Conditional("DEBUG")]` * Reference `IServiceProvider` via `Application.Current?.Handler.MauiContext` * Goto correct shell route * Leverage overloaded method * Change `Debug.WriteLine` -> `Trace.WriteLine` * Add Null Coalescing Operator to `Handler` --------- Co-authored-by: Vladislav Antonyuk <[email protected]>
1 parent c555e90 commit d1825b6

File tree

6 files changed

+132
-2
lines changed

6 files changed

+132
-2
lines changed

samples/CommunityToolkit.Maui.Markup.Sample/AppShell.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ public AppShell(NewsPage newsPage)
1717
public static string GetRoute<TPage, TViewModel>() where TPage : BaseContentPage<TViewModel>
1818
where TViewModel : BaseViewModel
1919
{
20-
if (!pageRouteMappingDictionary.TryGetValue(typeof(TPage), out var route))
20+
return GetRoute(typeof(TPage));
21+
}
22+
23+
public static string GetRoute(Type type)
24+
{
25+
if (!pageRouteMappingDictionary.TryGetValue(type, out var route))
2126
{
22-
throw new KeyNotFoundException($"No map for ${typeof(TPage)} was found on navigation mappings. Please register your ViewModel in {nameof(AppShell)}.{nameof(pageRouteMappingDictionary)}");
27+
throw new KeyNotFoundException($"No map for ${type} was found on navigation mappings. Please register your ViewModel in {nameof(AppShell)}.{nameof(pageRouteMappingDictionary)}");
2328
}
2429

2530
return route;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System.Diagnostics;
2+
using System.Diagnostics.CodeAnalysis;
3+
4+
namespace CommunityToolkit.Maui.Markup.Sample;
5+
6+
class HotReloadHandler : ICommunityToolkitHotReloadHandler
7+
{
8+
public async void OnHotReload(IReadOnlyList<Type> types)
9+
{
10+
if (Application.Current?.Windows is null)
11+
{
12+
Trace.WriteLine($"{nameof(HotReloadHandler)} Failed: {nameof(Application)}.{nameof(Application.Current)}.{nameof(Application.Current.Windows)} is null");
13+
return;
14+
}
15+
16+
foreach (var window in Application.Current.Windows)
17+
{
18+
if (window.Page is not Page currentPage)
19+
{
20+
return;
21+
}
22+
23+
foreach (var type in types)
24+
{
25+
if (type.IsSubclassOf(typeof(Page)))
26+
{
27+
if (window.Page is AppShell shell)
28+
{
29+
if (shell.CurrentPage is Page visiblePage
30+
&& visiblePage.GetType() == type)
31+
{
32+
var currentPageShellRoute = AppShell.GetRoute(type);
33+
34+
await currentPage.Dispatcher.DispatchAsync(async () =>
35+
{
36+
await shell.GoToAsync(currentPageShellRoute, false);
37+
shell.Navigation.RemovePage(visiblePage);
38+
});
39+
40+
break;
41+
}
42+
}
43+
else
44+
{
45+
if (TryGetModalStackPage(window, out var modalPage))
46+
{
47+
await currentPage.Dispatcher.DispatchAsync(async () =>
48+
{
49+
await currentPage.Navigation.PopModalAsync(false);
50+
await currentPage.Navigation.PushModalAsync(modalPage, false);
51+
});
52+
}
53+
else
54+
{
55+
await currentPage.Dispatcher.DispatchAsync(async () =>
56+
{
57+
await currentPage.Navigation.PopAsync(false);
58+
await currentPage.Navigation.PushAsync(modalPage, false);
59+
});
60+
}
61+
62+
break;
63+
}
64+
}
65+
}
66+
}
67+
}
68+
69+
70+
static bool TryGetModalStackPage(Window window, [NotNullWhen(true)] out Page? page)
71+
{
72+
page = window.Navigation.ModalStack.LastOrDefault();
73+
return page is not null;
74+
}
75+
}

samples/CommunityToolkit.Maui.Markup.Sample/MauiProgram.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public static MauiApp CreateMauiApp()
3333
builder.Services.AddTransient<SettingsPage, SettingsViewModel>();
3434
builder.Services.AddTransient<NewsDetailPage, NewsDetailViewModel>();
3535

36+
// C# Hot Reload Handler
37+
builder.Services.AddSingleton<ICommunityToolkitHotReloadHandler, HotReloadHandler>();
38+
3639
return builder.Build();
3740

3841
static TimeSpan sleepDurationProvider(int attemptNumber) => TimeSpan.FromSeconds(Math.Pow(2, attemptNumber));

src/CommunityToolkit.Maui.Markup/AppBuilderExtensions.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ public static class AppBuilderExtensions
1212
/// <returns><see cref="MauiAppBuilder"/> initialized for <see cref="CommunityToolkit.Maui.Markup"/></returns>
1313
public static MauiAppBuilder UseMauiCommunityToolkitMarkup(this MauiAppBuilder builder)
1414
{
15+
RegisterReloadApplicationEventHandler();
1516
return builder;
1617
}
18+
19+
[System.Diagnostics.Conditional("DEBUG")]
20+
static void RegisterReloadApplicationEventHandler()
21+
{
22+
CommunityToolkitMetadataUpdateHandler.ReloadApplication += ReloadApplication;
23+
}
24+
25+
static void ReloadApplication(object? sender, IReadOnlyList<Type> e)
26+
{
27+
var hotReloadHandler = Application.Current?.Handler?.MauiContext?.Services.GetService<ICommunityToolkitHotReloadHandler>();
28+
hotReloadHandler?.OnHotReload(e);
29+
}
1730
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[assembly: System.Reflection.Metadata.MetadataUpdateHandler(typeof(CommunityToolkit.Maui.Markup.CommunityToolkitMetadataUpdateHandler))]
2+
namespace CommunityToolkit.Maui.Markup;
3+
4+
static class CommunityToolkitMetadataUpdateHandler
5+
{
6+
static readonly WeakEventManager reloadApplicationEventHandler = new();
7+
8+
public static event EventHandler<IReadOnlyList<Type>> ReloadApplication
9+
{
10+
add => reloadApplicationEventHandler.AddEventHandler(value);
11+
remove => reloadApplicationEventHandler.RemoveEventHandler(value);
12+
}
13+
14+
[System.Diagnostics.Conditional("DEBUG")]
15+
static void UpdateApplication(Type[]? types)
16+
{
17+
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
18+
reloadApplicationEventHandler.HandleEvent(null, types?.ToList() ?? Enumerable.Empty<Type>(), nameof(ReloadApplication));
19+
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
20+
}
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace CommunityToolkit.Maui.Markup;
2+
3+
/// <summary>
4+
/// Interface for handling C# Hot Reload requests
5+
/// </summary>
6+
public interface ICommunityToolkitHotReloadHandler
7+
{
8+
/// <summary>
9+
/// Executes when C# Hot Reload is invoked
10+
/// </summary>
11+
/// <param name="types">A <see cref="IReadOnlyList{Type}"/> contianing the types that have been changed in code.</param>
12+
void OnHotReload(IReadOnlyList<Type> types);
13+
}

0 commit comments

Comments
 (0)