diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD index 065c9af..1a49b64 100644 --- a/CONTRIBUTING.MD +++ b/CONTRIBUTING.MD @@ -9,7 +9,9 @@ If an issue doesn't already exist, it is probably a good idea to raise an issue on the GitHub for the bug/feature suggestion (see above). If you were to spend time making changes without doing this, then your pull request may not be accepted if the change is not wanted or it is implemented in a way that the project does not want. Especially for more significant changes, it is always better to discuss a proposed implementation on an issue first. ## Pre-requisites -- [Visual Studio 2022 - 17.7.0 or greater](https://visualstudio.microsoft.com/vs/community/) with the .NET Multi-platform App UI workload installed +- [Visual Studio 2022 - 17.7.0 or greater](https://visualstudio.microsoft.com/vs/community/) + - Required: the .NET Multi-platform App UI workload installed + - Optional: the Visual Studio extension development workload installed (for debugging source generators) - [XAML Styler](https://marketplace.visualstudio.com/items?itemName=TeamXavalon.XAMLStyler2022) for ensuring XAML is formatted consistently. It is a good idea to set it to run automatically on save. NOTE: The above software is the recommended basics. You may be able to use other software versions or different pieces of software to contribute to this project. @@ -34,4 +36,10 @@ NOTE: The above software is the recommended basics. You may be able to use other - Try to rebase (`git rebase -i`) your PR onto `main` to tidy your git history https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase - Update the unit tests (fix any existing tests and ensure new code has unit test coverage) - Update the sample app with the change/feature in action -- Ensure you have the [XAML Styler](https://marketplace.visualstudio.com/items?itemName=TeamXavalon.XAMLStyler2022) extension installed and have run it on any XAML files you have changed to format them \ No newline at end of file +- Ensure you have the [XAML Styler](https://marketplace.visualstudio.com/items?itemName=TeamXavalon.XAMLStyler2022) extension installed and have run it on any XAML files you have changed to format them + +## Source generators +### Debugging source generators +1. Set `Burkus.Mvvm.Maui.SourceGenerators` as your startup project. +2. Start debugging by running "Debug Source Gen Demo App" Debug mode. +3. Your breakpoints will now work in the source generator project. \ No newline at end of file diff --git a/README.md b/README.md index b1c14c9..0a6a772 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,9 @@ See the `DemoApp` in the `/samples` folder of this repository for a full example ## Getting started 1. Install `Burkus.Mvvm.Maui` into your main MAUI project from NuGet: [![NuGet](https://img.shields.io/nuget/v/Burkus.Mvvm.Maui.svg?label=NuGet)](https://www.nuget.org/packages/Burkus.Mvvm.Maui/) -2. In your shared project's `App.xaml.cs`, remove any line where `MainPage` is set to a `Page` or an `AppShell`. Make `App` inherit from `BurkusMvvmApplication`. You should be left with a simpler `App` class like this: +2. In your shared project's `App.xaml.cs`, remove any line where `MainPage` is set to a `Page` or an `AppShell`. You should be left with a simpler `App` class like this: ``` csharp -public partial class App : BurkusMvvmApplication +public partial class App : Application { public App() { @@ -63,16 +63,7 @@ public partial class App : BurkusMvvmApplication } } ``` -3. Update `App.xaml` in your shared project to be a `burkus:BurkusMvvmApplication`. -``` xml - - - ... - -``` -4. In your `MauiProgram.cs` file, call `.UseBurkusMvvm()` in your builder creation e.g.: +3. In your `MauiProgram.cs` file, call `.UseBurkusMvvm()` in your builder creation e.g.: ```csharp public static class MauiProgram @@ -90,7 +81,7 @@ public static class MauiProgram }) ... ``` -5. **💡 RECOMMENDED**: This library pairs great with the amazing `CommunityToolkit.Mvvm`. Follow its [Getting started](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/#getting-started) guide to add it. +4. **💡 RECOMMENDED**: This library pairs great with the amazing `CommunityToolkit.Mvvm`. Follow its [Getting started](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/#getting-started) guide to add it. ## Registering views, viewmodels, and services A recommended way to register your views, viewmodels, and services is by creating extension methods in your `MauiProgram.cs` file. @@ -411,7 +402,8 @@ See the [IDialogService interface in the repository](https://github.com/BurkusCa ## Advanced / complexities The below are some things of note that may help prevent issues from arising: -- When you inherit from `BurkusMvvmApplication`, the `MainPage` of the app will be automatically set to a `NavigationPage`. This means the first page you push can be a `ContentPage` rather than needing to push a `NavigationPage`. This may change in the future. +- The `MainPage` of the app will be automatically set to a `NavigationPage`. This means the first page you push can be a `ContentPage` rather than needing to push a `NavigationPage`. This may change in the future. +- A source generator will automatically add code overriding `Window CreateWindow(IActivationState? activationState)` in your `App.xaml.cs` class. - Adding this package to a project will automatically import the `Burkus.Mvvm.Maui` namespace globally if you have [`ImplicitUsings`](https://devblogs.microsoft.com/dotnet/welcome-to-csharp-10/#implicit-usings) enabled in your project. You can opt out of this by including the following in your `.csproj` file: ``` xml diff --git a/samples/DemoApp/App.xaml b/samples/DemoApp/App.xaml index 12ddef4..39d6bc3 100644 --- a/samples/DemoApp/App.xaml +++ b/samples/DemoApp/App.xaml @@ -1,11 +1,9 @@  - - + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> + @@ -13,5 +11,5 @@ - - + + diff --git a/samples/DemoApp/App.xaml.cs b/samples/DemoApp/App.xaml.cs index addcb8d..b2532ab 100644 --- a/samples/DemoApp/App.xaml.cs +++ b/samples/DemoApp/App.xaml.cs @@ -1,6 +1,6 @@ namespace DemoApp; -public partial class App : BurkusMvvmApplication +public partial class App : Application { public App() { diff --git a/samples/DemoApp/DemoApp.csproj b/samples/DemoApp/DemoApp.csproj index d5f37b2..d509f54 100644 --- a/samples/DemoApp/DemoApp.csproj +++ b/samples/DemoApp/DemoApp.csproj @@ -1,164 +1,173 @@  - - net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst - $(TargetFrameworks);net7.0-windows10.0.19041.0 - - - Exe - DemoApp - true - true - enable - - - DemoApp - - - uk.co.burkus.demoapp - 8594242f-7e2e-4a34-b978-231d9a424d1f - - - 1.0 - 1 - - 11.0 - 13.1 - 21.0 - 10.0.17763.0 - 10.0.17763.0 - 6.5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - ChangeUsernamePage.xaml - - - TodoPage.xaml - - - RemindersPage.xaml - - - ContactsPage.xaml - - - DemoFlyoutPage.xaml - - - UriTestPage.xaml - - - CharlieTabPage.xaml - - - BetaTabPage.xaml - - - DemoTabsPage.xaml - - - RegisterPage.xaml - - - AlphaTabPage.xaml - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - - MSBuild:Compile - - + + net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst + $(TargetFrameworks);net7.0-windows10.0.19041.0 + + + Exe + DemoApp + true + true + enable + + + DemoApp + + + uk.co.burkus.demoapp + 8594242f-7e2e-4a34-b978-231d9a424d1f + + + 1.0 + 1 + + 11.0 + 13.1 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + 6.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + ChangeUsernamePage.xaml + + + TodoPage.xaml + + + RemindersPage.xaml + + + ContactsPage.xaml + + + DemoFlyoutPage.xaml + + + UriTestPage.xaml + + + CharlieTabPage.xaml + + + BetaTabPage.xaml + + + DemoTabsPage.xaml + + + RegisterPage.xaml + + + AlphaTabPage.xaml + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + diff --git a/src/Burkus.Mvvm.Maui.SourceGenerators/AppSourceGenerator.cs b/src/Burkus.Mvvm.Maui.SourceGenerators/AppSourceGenerator.cs index 820cf3d..39309c5 100644 --- a/src/Burkus.Mvvm.Maui.SourceGenerators/AppSourceGenerator.cs +++ b/src/Burkus.Mvvm.Maui.SourceGenerators/AppSourceGenerator.cs @@ -1,6 +1,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; +using System; using System.Linq; using System.Text; @@ -26,35 +27,55 @@ public void Execute(GeneratorExecutionContext context) // check if the App class is partial and inherits from Application var appSymbol = semanticModel.GetDeclaredSymbol(receiver.AppClass); - if (appSymbol is null || !isPartial || !appSymbol.BaseType.Equals(semanticModel.Compilation.GetTypeByMetadataName("Application"))) - return; + if (appSymbol is null || !isPartial || !appSymbol.BaseType.Equals(semanticModel.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Application"))) + { + throw new Exception("You must have a partial class called \"App\" that inherits from Application in your .NET MAUI project."); + } + + // get the MAUI program we are running in + + var assembly = context.Compilation.Assembly; + var mauiProgramName = $"{assembly.Name}.MauiProgram"; + var mauiProgram = context.Compilation + .GetTypeByMetadataName(mauiProgramName); + + if (mauiProgram is null) + { + throw new Exception("You must have a class called \"MauiProgram\" in your .NET MAUI project."); + } // generate the source code for the CreateWindow method - var sourceBuilder = """ - using System; - partial class App - { - protected override Window CreateWindow(IActivationState? activationState) - { - Current.MainPage = new NavigationPage(); - - var burkusMvvmBuilder = ServiceResolver.Resolve(); - var navigationService = ServiceResolver.Resolve(); - var serviceProvider = ServiceResolver.GetServiceProvider(); - - // perform the user's desired initialization logic - if (burkusMvvmBuilder.onStartFunc != null) - { - burkusMvvmBuilder.onStartFunc.Invoke(navigationService, serviceProvider); - } - - return base.CreateWindow(activationState); - } - } - """; + var sourceBuilder = $@"// +// This code was generated by a tool. Burkus.Mvvm.Maui generated this. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// + +namespace {mauiProgram.ContainingNamespace.ToDisplayString()}; + +public partial class App : Application +{{ + protected override Window CreateWindow(IActivationState? activationState) + {{ + Current.MainPage = new NavigationPage(); + + var burkusMvvmBuilder = ServiceResolver.Resolve(); + var navigationService = ServiceResolver.Resolve(); + var serviceProvider = ServiceResolver.GetServiceProvider(); + + // perform the user's desired initialization logic + if (burkusMvvmBuilder.onStartFunc != null) + {{ + burkusMvvmBuilder.onStartFunc.Invoke(navigationService, serviceProvider); + }} + + return base.CreateWindow(activationState); + }} +}}"; // add the generated source file to the compilation - context.AddSource("AppGenerator", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + context.AddSource("App-BurkusMvvmApplication.g", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); } } diff --git a/src/Burkus.Mvvm.Maui.SourceGenerators/Burkus.Mvvm.Maui.SourceGenerators.csproj b/src/Burkus.Mvvm.Maui.SourceGenerators/Burkus.Mvvm.Maui.SourceGenerators.csproj index c28e4a8..d2c7fc6 100644 --- a/src/Burkus.Mvvm.Maui.SourceGenerators/Burkus.Mvvm.Maui.SourceGenerators.csproj +++ b/src/Burkus.Mvvm.Maui.SourceGenerators/Burkus.Mvvm.Maui.SourceGenerators.csproj @@ -1,13 +1,17 @@ - - netstandard2.0 - 11.0 - true - + + netstandard2.0 + true + 11.0 + true + - - - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/Burkus.Mvvm.Maui.SourceGenerators/Properties/launchSettings.json b/src/Burkus.Mvvm.Maui.SourceGenerators/Properties/launchSettings.json new file mode 100644 index 0000000..bf1c442 --- /dev/null +++ b/src/Burkus.Mvvm.Maui.SourceGenerators/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Debug Source Gen Demo App": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\..\\samples\\DemoApp\\DemoApp.csproj" + } + } +} \ No newline at end of file diff --git a/src/Burkus.Mvvm.Maui/Abstractions/IBurkusMvvmBuilder.cs b/src/Burkus.Mvvm.Maui/Abstractions/IBurkusMvvmBuilder.cs index 78279b7..615a574 100644 --- a/src/Burkus.Mvvm.Maui/Abstractions/IBurkusMvvmBuilder.cs +++ b/src/Burkus.Mvvm.Maui/Abstractions/IBurkusMvvmBuilder.cs @@ -1,6 +1,12 @@ namespace Burkus.Mvvm.Maui; -internal interface IBurkusMvvmBuilder +/// +/// Internal Burkus.Mvvm.Maui use only. Not intended for consuming project usage. +/// +public interface IBurkusMvvmBuilder { + /// + /// Internal Burkus.Mvvm.Maui use only. Not intended for consuming project usage. + /// Func onStartFunc { get; set; } } diff --git a/src/Burkus.Mvvm.Maui/Burkus.Mvvm.Maui.csproj b/src/Burkus.Mvvm.Maui/Burkus.Mvvm.Maui.csproj index e368409..418534a 100644 --- a/src/Burkus.Mvvm.Maui/Burkus.Mvvm.Maui.csproj +++ b/src/Burkus.Mvvm.Maui/Burkus.Mvvm.Maui.csproj @@ -34,21 +34,6 @@ README.md - - - true - Generated - - - - - - - - - True @@ -67,6 +52,6 @@ - - + + diff --git a/src/Burkus.Mvvm.Maui/Models/BurkusMvvmApplication.cs b/src/Burkus.Mvvm.Maui/Models/BurkusMvvmApplication.cs deleted file mode 100644 index 3769479..0000000 --- a/src/Burkus.Mvvm.Maui/Models/BurkusMvvmApplication.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Burkus.Mvvm.Maui; - -public class BurkusMvvmApplication : Application -{ - protected override Window CreateWindow(IActivationState? activationState) - { - Current.MainPage = new NavigationPage(); - - var burkusMvvmBuilder = ServiceResolver.Resolve(); - var navigationService = ServiceResolver.Resolve(); - var serviceProvider = ServiceResolver.GetServiceProvider(); - - // perform the user's desired initialization logic - if (burkusMvvmBuilder.onStartFunc != null) - { - burkusMvvmBuilder.onStartFunc.Invoke(navigationService, serviceProvider); - } - - return base.CreateWindow(activationState); - } -} diff --git a/src/Burkus.Mvvm.Maui/Services/ServiceResolver.cs b/src/Burkus.Mvvm.Maui/Services/ServiceResolver.cs index df73016..94e9700 100644 --- a/src/Burkus.Mvvm.Maui/Services/ServiceResolver.cs +++ b/src/Burkus.Mvvm.Maui/Services/ServiceResolver.cs @@ -10,7 +10,7 @@ internal static void RegisterScope( scope = serviceScope; } - internal static IServiceProvider GetServiceProvider() + public static IServiceProvider GetServiceProvider() { return scope.ServiceProvider; }