Skip to content

Commit

Permalink
Merge pull request #58 from egvijayanand/working
Browse files Browse the repository at this point in the history
Sample project to demonstrate C# Hot Reload
  • Loading branch information
egvijayanand authored Nov 3, 2022
2 parents 712daed + b757756 commit 7ec2906
Show file tree
Hide file tree
Showing 36 changed files with 1,179 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Available under the `src` directory:
- Both Xamarin.Forms and .NET MAUI from a single project - `DateCalculator.UI`
* `EmbeddedAndroid` - .NET MAUI Page embedded in a Native Android App, targeting .NET 6 (`net6.0-android`)
* `MediaElement` - Sample project in both .NET 6 and 7. Now made available in Preview bits as part of the .NET MAUI CommunityToolkit - And it'll be a separate NuGet package titled `CommunityToolkit.Maui.MediaElement`
* `MauiHotReload` - Sample project to demonstrate **C# Hot Reload** feature supported via [MetadataUpdateHandler](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.metadata.metadataupdatehandlerattribute?view=net-6.0) (refer to HotReloadService.cs). Core logic is abstracted into a base page named `MauiPage`, inherit the content pages from it and implement the UI logic in the override of the abstract `Build()` method. Source is available in the `src\MauiHotReload` folder.

* C# Samples - C# version of the [.NET MAUI UI Challenge](https://aka.ms/maui/UIChallenge) - [Awesome UIs](https://github.com/jsuarezruiz/dotnet-maui-showcase) without any XAML usage - Stay tuned for more samples ...
- Made available under the `src/C#-Samples/` folder
Expand Down
71 changes: 71 additions & 0 deletions src/MauiHotReload/App.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Reflection;

namespace MauiHotReload
{
public partial class App : Application
{
public App()
{
Initialize();

MainPage = new NavigationPage(new HomePage());
}

private void Initialize()
{
var assembly = GetType().Assembly;
Resources.MergedDictionaries.Add(GetXamlResourceDictionary("Resources/Colors.xaml", assembly));
Resources.MergedDictionaries.Add(GetXamlResourceDictionary("Resources/Styles.xaml", assembly));
Resources.Add("ApplePadding", new Thickness(30, 60, 30, 30));
Resources.Add("DefaultPadding", new Thickness(30));
Resources.Add("ItemSpacing", 10d);
Resources.Add(new Style(typeof(StackBase))
{
ApplyToDerivedTypes = true,
Setters =
{
new() { Property = StackBase.SpacingProperty, Value = AppResource<double>("ItemSpacing") },
},
});
Resources.Add(new Style(typeof(ContentPage))
{
ApplyToDerivedTypes = true,
Setters =
{
new() { Property = Page.PaddingProperty, Value = DeviceInfo.Idiom.ToString() switch { nameof(DeviceIdiom.Phone) => AppResource<Thickness>("ApplePadding"), _ => AppResource<Thickness>("DefaultPadding") } },
},
});
Resources.Add("MauiLabel", new Style(typeof(Label))
{
Setters =
{
new() { Property = Label.TextColorProperty, Value = RequestedTheme switch { AppTheme.Dark => AppResource<Color>("White"), AppTheme.Light or _ => AppResource<Color>("Primary") } },
},
});
Resources.Add("Action", new Style(typeof(Button))
{
Setters =
{
new() { Property = Button.BackgroundColorProperty, Value = RequestedTheme switch { AppTheme.Dark => AppResource<Color>("BackgroundDark"), AppTheme.Light or _ => AppResource<Color>("BackgroundLight") } },
new() { Property = Button.TextColorProperty, Value = RequestedTheme switch { AppTheme.Dark => AppResource<Color>("TextDark"), AppTheme.Light or _ => AppResource<Color>("TextLight") } },
new() { Property = Button.FontFamilyProperty, Value = AppResource<string>("AppFont") },
new() { Property = Button.FontSizeProperty, Value = AppResource<double>("AppFontSize") },
new() { Property = Button.PaddingProperty, Value = new Thickness(14,10) },
},
});
Resources.Add("PrimaryAction", new Style(typeof(Button))
{
BasedOn = AppResource<Style>("Action"),
Setters =
{
new() { Property = Button.BackgroundColorProperty, Value = AppResource<Color>("Primary") },
new() { Property = Button.FontAttributesProperty, Value = FontAttributes.Bold },
new() { Property = Button.TextColorProperty, Value = AppResource<Color>("White") },
},
});
}

private static ResourceDictionary GetXamlResourceDictionary(string resourcePath, Assembly assembly)
=> GetXamlResource<ResourceDictionary>(resourcePath, assembly);
}
}
20 changes: 20 additions & 0 deletions src/MauiHotReload/DetailsPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace MauiHotReload
{
public class DetailsPage : MauiPage
{
protected override void Build()
{
Title = "Details";
Content = new Grid()
{
Children =
{
new Label()
{
Text = "Details Page"
}.Center()
}
};
}
}
}
44 changes: 44 additions & 0 deletions src/MauiHotReload/HomePage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace MauiHotReload
{
public class HomePage : MauiPage
{
protected override void Build()
{
Title = "Home";
Content = new Grid
{
RowDefinitions = Rows.Define(Star, Star, Star, Star),
ColumnDefinitions = Columns.Define(Star, Star),
Children =
{
new Label
{
Text = "Landing Page",
}.Row(0).ColumnSpan(2).Center(),
new Label
{
Text = "Hot reloaded label in row 2 spanning two columns",
}.Row(1).ColumnSpan(2).Center(),
new Button
{
Text = "Go to details",
Style = AppResource<Style>("PrimaryAction"),
}.Row(2).Column(0).Center().Invoke(btn => btn.Clicked += OnClicked),
new Label
{
Text = "Hot reloaded label in row 3 column 2 with text aligned at the end",
}.Row(2).Column(1).End().CenterVertical(),
new Label
{
Text = "Hot reloaded label in row 4 spanning two columns",
}.Row(3).ColumnSpan(2).Center()
}
};
}

private async void OnClicked(object? sender, EventArgs e)
{
await Navigation.PushAsync(new DetailsPage());
}
}
}
20 changes: 20 additions & 0 deletions src/MauiHotReload/HotReloadService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#if DEBUG
using System.Reflection.Metadata;

[assembly: MetadataUpdateHandler(typeof(MauiHotReload.HotReloadService))]

namespace MauiHotReload
{
public static class HotReloadService
{
//#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public static event Action<Type[]?>? UpdateApplicationEvent;
//#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

internal static void ClearCache(Type[]? types) { }

internal static void UpdateApplication(Type[]? types)
=> UpdateApplicationEvent?.Invoke(types);
}
}
#endif
6 changes: 6 additions & 0 deletions src/MauiHotReload/Imports.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Markup
global using CommunityToolkit.Maui.Markup;
// Static
global using static CommunityToolkit.Maui.Markup.GridRowsColumns;
global using static Microsoft.Maui.Graphics.Colors;
global using static VijayAnand.Toolkit.Markup.ResourceHelper;
15 changes: 15 additions & 0 deletions src/MauiHotReload/MauiHotReload.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"folders": [
{
"path": "."
}
],
"settings": {},
"extensions": {
"recommendations": [
"ms-dotnettools.csharp",
"clancey.comet-debug",
"VisualStudioExptTeam.vscodeintellicode"
]
}
}
64 changes: 64 additions & 0 deletions src/MauiHotReload/MauiHotReload.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0-android;net6.0-ios;net6.0-maccatalyst</TargetFrameworks>
<!-- Targets possible from Windows OS -->
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net6.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the Tizen app. You will need to install Tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType>

<!-- .NET MAUI -->
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>

<!-- Project Options -->
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MauiHotReload</RootNamespace>

<!-- Display name -->
<ApplicationTitle>MauiHotReload</ApplicationTitle>

<!-- App Identifier -->
<ApplicationId>com.companyname.mauihotreload</ApplicationId>
<ApplicationIdGuid>080d8082-5270-4fd1-bfe9-7b5143f53696</ApplicationIdGuid>

<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>

<!-- Target Platform Options -->
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>

<!-- Minimum Target OS Version for Windows Platform -->
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
</PropertyGroup>

<ItemGroup>
<!-- App Icon -->
<MauiIcon Include="Resources\appicon.svg" ForegroundFile="Resources\appiconfg.svg" Color="#512BD4" />
<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\appiconfg.svg" Color="#512BD4" BaseSize="128,128" />
<!-- Images -->
<MauiImage Include="Resources\Images\*" />
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208" />
<!-- Custom Fonts -->
<MauiFont Include="Resources\Fonts\*" />
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

<ItemGroup>
<None Remove="MauiHotReload.code-workspace" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="CommunityToolkit.Maui.Markup" Version="1.2.0" />
<PackageReference Include="VijayAnand.Toolkit.Markup" Version="1.0.1-pre1" />
</ItemGroup>

</Project>
28 changes: 28 additions & 0 deletions src/MauiHotReload/MauiHotReload.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31611.283
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiHotReload", "MauiHotReload.csproj", "{579DBAEF-3844-4DAC-B61D-A26EA353C2C3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{579DBAEF-3844-4DAC-B61D-A26EA353C2C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{579DBAEF-3844-4DAC-B61D-A26EA353C2C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{579DBAEF-3844-4DAC-B61D-A26EA353C2C3}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{579DBAEF-3844-4DAC-B61D-A26EA353C2C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{579DBAEF-3844-4DAC-B61D-A26EA353C2C3}.Release|Any CPU.Build.0 = Release|Any CPU
{579DBAEF-3844-4DAC-B61D-A26EA353C2C3}.Release|Any CPU.Deploy.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {61F7FB11-1E47-470C-91E2-47F8143E1572}
SolutionGuid = {EC2EA614-E633-4058-8E91-AE765501F5FB}
EndGlobalSection
EndGlobal
47 changes: 47 additions & 0 deletions src/MauiHotReload/MauiPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace MauiHotReload
{
public abstract class MauiPage : ContentPage
{
public MauiPage()
{
Build();
#if DEBUG
if (Application.Current is not null)
{
// Not a NavigationPage / TabbedPage / Shell
if (Application.Current.MainPage is ContentPage)
{
HotReloadService.UpdateApplicationEvent += Refresh;
}
}
#endif
}

protected abstract void Build();

protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
#if DEBUG
HotReloadService.UpdateApplicationEvent += Refresh;
#endif
}

protected override void OnNavigatedFrom(NavigatedFromEventArgs args)
{
base.OnNavigatedFrom(args);
#if DEBUG
HotReloadService.UpdateApplicationEvent -= Refresh;
#endif
}

private void Refresh(Type[]? obj) => Dispatcher.Dispatch(Build);

public void Dispose()
{
#if DEBUG
HotReloadService.UpdateApplicationEvent -= Refresh;
#endif
}
}
}
19 changes: 19 additions & 0 deletions src/MauiHotReload/MauiProgram.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace MauiHotReload
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>()
.UseMauiCommunityToolkitMarkup()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-SemiBold.ttf", "OpenSansSemiBold");
});

return builder.Build();
}
}
}
6 changes: 6 additions & 0 deletions src/MauiHotReload/Platforms/Android/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
12 changes: 12 additions & 0 deletions src/MauiHotReload/Platforms/Android/MainActivity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Android.App;
using Android.Content.PM;
using Android.OS;

namespace MauiHotReload
{
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
public class MainActivity : MauiAppCompatActivity
{

}
}
16 changes: 16 additions & 0 deletions src/MauiHotReload/Platforms/Android/MainApplication.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Android.App;
using Android.Runtime;

namespace MauiHotReload
{
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}

protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<color name="colorPrimary">#512BD4</color>
<color name="colorPrimaryDark">#2B0B98</color>
<color name="colorAccent">#2B0B98</color>
</resources>
Loading

0 comments on commit 7ec2906

Please sign in to comment.