Skip to content

Commit

Permalink
Add Support for C# Hot Reload (#232)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
brminnick and VladislavAntonyuk authored Oct 1, 2023
1 parent c555e90 commit d1825b6
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 2 deletions.
9 changes: 7 additions & 2 deletions samples/CommunityToolkit.Maui.Markup.Sample/AppShell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ public AppShell(NewsPage newsPage)
public static string GetRoute<TPage, TViewModel>() where TPage : BaseContentPage<TViewModel>
where TViewModel : BaseViewModel
{
if (!pageRouteMappingDictionary.TryGetValue(typeof(TPage), out var route))
return GetRoute(typeof(TPage));
}

public static string GetRoute(Type type)
{
if (!pageRouteMappingDictionary.TryGetValue(type, out var route))
{
throw new KeyNotFoundException($"No map for ${typeof(TPage)} was found on navigation mappings. Please register your ViewModel in {nameof(AppShell)}.{nameof(pageRouteMappingDictionary)}");
throw new KeyNotFoundException($"No map for ${type} was found on navigation mappings. Please register your ViewModel in {nameof(AppShell)}.{nameof(pageRouteMappingDictionary)}");
}

return route;
Expand Down
75 changes: 75 additions & 0 deletions samples/CommunityToolkit.Maui.Markup.Sample/HotReloadHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace CommunityToolkit.Maui.Markup.Sample;

class HotReloadHandler : ICommunityToolkitHotReloadHandler
{
public async void OnHotReload(IReadOnlyList<Type> types)
{
if (Application.Current?.Windows is null)
{
Trace.WriteLine($"{nameof(HotReloadHandler)} Failed: {nameof(Application)}.{nameof(Application.Current)}.{nameof(Application.Current.Windows)} is null");
return;
}

foreach (var window in Application.Current.Windows)
{
if (window.Page is not Page currentPage)
{
return;
}

foreach (var type in types)
{
if (type.IsSubclassOf(typeof(Page)))
{
if (window.Page is AppShell shell)
{
if (shell.CurrentPage is Page visiblePage
&& visiblePage.GetType() == type)
{
var currentPageShellRoute = AppShell.GetRoute(type);

await currentPage.Dispatcher.DispatchAsync(async () =>
{
await shell.GoToAsync(currentPageShellRoute, false);
shell.Navigation.RemovePage(visiblePage);
});

break;
}
}
else
{
if (TryGetModalStackPage(window, out var modalPage))
{
await currentPage.Dispatcher.DispatchAsync(async () =>
{
await currentPage.Navigation.PopModalAsync(false);
await currentPage.Navigation.PushModalAsync(modalPage, false);
});
}
else
{
await currentPage.Dispatcher.DispatchAsync(async () =>
{
await currentPage.Navigation.PopAsync(false);
await currentPage.Navigation.PushAsync(modalPage, false);
});
}

break;
}
}
}
}
}


static bool TryGetModalStackPage(Window window, [NotNullWhen(true)] out Page? page)
{
page = window.Navigation.ModalStack.LastOrDefault();
return page is not null;
}
}
3 changes: 3 additions & 0 deletions samples/CommunityToolkit.Maui.Markup.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public static MauiApp CreateMauiApp()
builder.Services.AddTransient<SettingsPage, SettingsViewModel>();
builder.Services.AddTransient<NewsDetailPage, NewsDetailViewModel>();

// C# Hot Reload Handler
builder.Services.AddSingleton<ICommunityToolkitHotReloadHandler, HotReloadHandler>();

return builder.Build();

static TimeSpan sleepDurationProvider(int attemptNumber) => TimeSpan.FromSeconds(Math.Pow(2, attemptNumber));
Expand Down
13 changes: 13 additions & 0 deletions src/CommunityToolkit.Maui.Markup/AppBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ public static class AppBuilderExtensions
/// <returns><see cref="MauiAppBuilder"/> initialized for <see cref="CommunityToolkit.Maui.Markup"/></returns>
public static MauiAppBuilder UseMauiCommunityToolkitMarkup(this MauiAppBuilder builder)
{
RegisterReloadApplicationEventHandler();
return builder;
}

[System.Diagnostics.Conditional("DEBUG")]
static void RegisterReloadApplicationEventHandler()
{
CommunityToolkitMetadataUpdateHandler.ReloadApplication += ReloadApplication;
}

static void ReloadApplication(object? sender, IReadOnlyList<Type> e)
{
var hotReloadHandler = Application.Current?.Handler?.MauiContext?.Services.GetService<ICommunityToolkitHotReloadHandler>();
hotReloadHandler?.OnHotReload(e);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[assembly: System.Reflection.Metadata.MetadataUpdateHandler(typeof(CommunityToolkit.Maui.Markup.CommunityToolkitMetadataUpdateHandler))]
namespace CommunityToolkit.Maui.Markup;

static class CommunityToolkitMetadataUpdateHandler
{
static readonly WeakEventManager reloadApplicationEventHandler = new();

public static event EventHandler<IReadOnlyList<Type>> ReloadApplication
{
add => reloadApplicationEventHandler.AddEventHandler(value);
remove => reloadApplicationEventHandler.RemoveEventHandler(value);
}

[System.Diagnostics.Conditional("DEBUG")]
static void UpdateApplication(Type[]? types)
{
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
reloadApplicationEventHandler.HandleEvent(null, types?.ToList() ?? Enumerable.Empty<Type>(), nameof(ReloadApplication));
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace CommunityToolkit.Maui.Markup;

/// <summary>
/// Interface for handling C# Hot Reload requests
/// </summary>
public interface ICommunityToolkitHotReloadHandler
{
/// <summary>
/// Executes when C# Hot Reload is invoked
/// </summary>
/// <param name="types">A <see cref="IReadOnlyList{Type}"/> contianing the types that have been changed in code.</param>
void OnHotReload(IReadOnlyList<Type> types);
}

0 comments on commit d1825b6

Please sign in to comment.