diff --git a/src/SharedMauiCoreLibrary/Hosting/AppHostBuilderExtensions.cs b/src/SharedMauiCoreLibrary/Hosting/AppHostBuilderExtensions.cs
index 694b232..b192b04 100644
--- a/src/SharedMauiCoreLibrary/Hosting/AppHostBuilderExtensions.cs
+++ b/src/SharedMauiCoreLibrary/Hosting/AppHostBuilderExtensions.cs
@@ -1,7 +1,11 @@
-using AndreasReitberger.Shared.Core.Interfaces;
+using AndreasReitberger.Shared.Core.Dispatch;
+using AndreasReitberger.Shared.Core.Events;
+using AndreasReitberger.Shared.Core.Interfaces;
using AndreasReitberger.Shared.Core.Localization;
using AndreasReitberger.Shared.Core.NavigationManager;
using CommunityToolkit.Maui;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using System.Diagnostics;
using System.Runtime.Versioning;
namespace AndreasReitberger.Shared.Core.Hosting
@@ -12,9 +16,42 @@ namespace AndreasReitberger.Shared.Core.Hosting
[SupportedOSPlatform(SPlatforms.WindowsVersion)]
public static class AppHostBuilderExtensions
{
+ ///
+ /// Configures the core library features for a .NET MAUI application using the specified builder.
+ /// Included features:
+ /// - UseMauiCommunityToolkit()
+ /// - ConfigureDispatching()
+ ///
+ /// This method adds essential services and features from the core library, including the
+ /// .NET MAUI Community Toolkit and dispatching support. Call this method during application startup to ensure
+ /// required functionality is available throughout the app.
+ /// The instance to configure. Cannot be null.
+ /// The same instance, enabling method chaining.
public static MauiAppBuilder ConfigureCoreLibrary(this MauiAppBuilder builder)
{
- builder.UseMauiCommunityToolkit();
+ builder
+ .UseMauiCommunityToolkit()
+ .ConfigureDispatching()
+ .RegisterDispatcher()
+ ;
+ return builder;
+ }
+
+ public static MauiAppBuilder RegisterDispatcher(this MauiAppBuilder builder, IDispatcher? dispatcher = null)
+ {
+ dispatcher ??= builder.Services.BuildServiceProvider().GetService();
+ if (dispatcher is not null)
+ {
+ DispatchManager manager = new(dispatcher);
+#if DEBUG
+ manager.Error += (a, b) =>
+ {
+ if (b is DispatchErrorEventArgs args)
+ Debug.WriteLine($"{nameof(DispatchManager)}: Dispatch error occured: \n{args}");
+ };
+#endif
+ builder.Services.TryAddSingleton(manager);
+ }
return builder;
}
@@ -25,7 +62,7 @@ public static MauiAppBuilder ConfigureLocalizationManager(this MauiAppBuilder bu
BaseFlagImageUri = flagImageBaseDir
};
manager.SetLanguages(languages);
- builder.Services.AddSingleton(manager);
+ builder.Services.TryAddSingleton(manager);
return builder;
}
@@ -35,7 +72,7 @@ public static MauiAppBuilder ConfigureLocalizationManager(this MauiAppBuilder bu
{
BaseFlagImageUri = flagImageBaseDir
};
- builder.Services.AddSingleton(manager);
+ builder.Services.TryAddSingleton(manager);
return builder;
}
@@ -44,7 +81,7 @@ public static MauiAppBuilder ConfigureShellNavigator(this MauiAppBuilder builder
dispatcher ??= builder.Services.BuildServiceProvider().GetService();
ShellNavigator navigator = dispatcher is not null ? new(rootPage, dispatcher) : new(rootPage);
navigator.AvailableEntryPages = [.. entryPages ?? [ rootPage]];
- builder.Services.AddSingleton(navigator);
+ builder.Services.TryAddSingleton(navigator);
return builder;
}
}
diff --git a/src/SharedMauiCoreLibrary/Interfaces/IDispatchManager.cs b/src/SharedMauiCoreLibrary/Interfaces/IDispatchManager.cs
new file mode 100644
index 0000000..5e0bd1b
--- /dev/null
+++ b/src/SharedMauiCoreLibrary/Interfaces/IDispatchManager.cs
@@ -0,0 +1,28 @@
+namespace AndreasReitberger.Shared.Core.Interfaces
+{
+ public interface IDispatchManager
+ {
+ #region Instance
+ public static IDispatchManager? Instance { get; private set; }
+ #endregion
+
+ #region Properties
+ public IDispatcher? Dispatcher { get; set; }
+ #endregion
+
+ #region Methods
+ public void Dispatch(IDispatcher? dispatcher, Action action, bool forceUiThread = false);
+ public void Dispatch(Action action, bool forceUiThread = false);
+ public void Dispatch(IDispatcher? dispatcher, Func action, bool forceUiThread = false);
+ public void Dispatch(Func action, bool forceUiThread = false);
+ public Task DispatchAsync(IDispatcher? dispatcher, Action action, bool forceUiThread = false);
+ public Task DispatchAsync(Action action, bool forceUiThread = false);
+ public Task DispatchAsync(IDispatcher? dispatcher, Func action, bool forceUiThread = false);
+ public Task DispatchAsync(Func action, bool forceUiThread = false);
+ #endregion
+
+ #region Events
+ public event EventHandler? Error;
+ #endregion
+ }
+}
diff --git a/src/SharedMauiCoreLibrary/Models/Dispatch/DispatchManager.Events.cs b/src/SharedMauiCoreLibrary/Models/Dispatch/DispatchManager.Events.cs
new file mode 100644
index 0000000..3d83390
--- /dev/null
+++ b/src/SharedMauiCoreLibrary/Models/Dispatch/DispatchManager.Events.cs
@@ -0,0 +1,26 @@
+using AndreasReitberger.Shared.Core.Events;
+using AndreasReitberger.Shared.Core.Interfaces;
+
+namespace AndreasReitberger.Shared.Core.Dispatch
+{
+ public partial class DispatchManager : ObservableObject, IDispatchManager
+ {
+ #region Events
+ public event EventHandler? Error;
+
+ protected virtual void OnError()
+ {
+ Error?.Invoke(this, EventArgs.Empty);
+ }
+ protected virtual void OnError(DispatchErrorEventArgs e)
+ {
+ Error?.Invoke(this, e);
+ }
+ protected virtual void OnError(UnhandledExceptionEventArgs e)
+ {
+ Error?.Invoke(this, e);
+ }
+ #endregion
+
+ }
+}
diff --git a/src/SharedMauiCoreLibrary/Models/Dispatch/DispatchManager.cs b/src/SharedMauiCoreLibrary/Models/Dispatch/DispatchManager.cs
new file mode 100644
index 0000000..c3ca1d3
--- /dev/null
+++ b/src/SharedMauiCoreLibrary/Models/Dispatch/DispatchManager.cs
@@ -0,0 +1,233 @@
+using AndreasReitberger.Shared.Core.Interfaces;
+using System.Diagnostics;
+
+namespace AndreasReitberger.Shared.Core.Dispatch
+{
+ public partial class DispatchManager : ObservableObject, IDispatchManager
+ {
+
+ #region Instance
+ static IDispatchManager? _instance = null;
+#if NET9_0_OR_GREATER
+ static readonly Lock Lock = new();
+#else
+ static readonly object Lock = new();
+#endif
+ public static IDispatchManager Instance
+ {
+ get
+ {
+ lock (Lock)
+ {
+ _instance ??= new DispatchManager();
+ }
+ return _instance;
+ }
+ set
+ {
+ if (_instance == value) return;
+ lock (Lock)
+ {
+ _instance = value;
+ }
+ }
+ }
+ #endregion
+
+ #region Properties
+ [ObservableProperty]
+ public partial IDispatcher? Dispatcher { get; set; }
+ #endregion
+
+ #region Ctor
+ public DispatchManager()
+ {
+ Dispatcher ??= DispatcherProvider.Current.GetForCurrentThread();
+ }
+ public DispatchManager(IDispatcher? dispatcher) : this()
+ {
+ Dispatcher = dispatcher;
+ }
+ #endregion
+
+ #region Methods
+
+ public void Dispatch(IDispatcher? dispatcher, Action action, bool forceUiThread = false)
+ {
+ dispatcher ??= DispatcherProvider.Current.GetForCurrentThread();
+ if (dispatcher is null)
+ {
+#if DEBUG
+ // Show when a dispatching was done or not
+ Debug.WriteLine($"{nameof(DispatchManager)}: Dispatcher => The `Dispatcher` property was null!");
+#endif
+ return;
+ }
+#if DEBUG
+ // Show when a dispatching was done or not
+ Debug.WriteLine($"{nameof(DispatchManager)}: Dispatcher => '{(dispatcher.IsDispatchRequired ? "dispatched" : "not dispatched")}'");
+ if (dispatcher.IsDispatchRequired)
+ {
+ // Just for a breaking point
+ }
+#endif
+ if (dispatcher.IsDispatchRequired || forceUiThread)
+ {
+ dispatcher.Dispatch(() =>
+ {
+ try
+ {
+ action.Invoke();
+ }
+ catch (Exception exc)
+ {
+ OnError(new Events.DispatchErrorEventArgs(exc)
+ {
+ DispatchRequired = dispatcher.IsDispatchRequired
+ });
+ }
+ });
+ }
+ else
+ {
+ action.Invoke();
+ }
+ }
+ public void Dispatch(Action action, bool forceUiThread = false)
+ => Dispatch(dispatcher: Dispatcher, action: action, forceUiThread: forceUiThread);
+
+ public void Dispatch(IDispatcher? dispatcher, Func action, bool forceUiThread = false)
+ {
+ dispatcher ??= DispatcherProvider.Current.GetForCurrentThread();
+ if (dispatcher is null)
+ {
+#if DEBUG
+ // Show when a dispatching was done or not
+ Debug.WriteLine($"{nameof(DispatchManager)}: Dispatcher => The `Dispatcher` property was null!");
+#endif
+ return;
+ }
+#if DEBUG
+ // Show when a dispatching was done or not
+ Debug.WriteLine($"{nameof(DispatchManager)}: Dispatcher => '{(dispatcher.IsDispatchRequired ? "dispatched" : "not dispatched")}'");
+ if (dispatcher.IsDispatchRequired)
+ {
+ // Just for a breaking point
+ }
+#endif
+ if (dispatcher.IsDispatchRequired || forceUiThread)
+ {
+ dispatcher.Dispatch(() =>
+ {
+ try
+ {
+ action.Invoke();
+ }
+ catch (Exception exc)
+ {
+ OnError(new Events.DispatchErrorEventArgs(exc)
+ {
+ DispatchRequired = dispatcher.IsDispatchRequired
+ });
+ }
+ });
+ }
+ else
+ {
+ action.Invoke();
+ }
+ }
+ public void Dispatch(Func action, bool forceUiThread = false)
+ => Dispatch(dispatcher: Dispatcher, action: action, forceUiThread: forceUiThread);
+
+ public async Task DispatchAsync(IDispatcher? dispatcher, Action action, bool forceUiThread = false)
+ {
+ dispatcher ??= DispatcherProvider.Current.GetForCurrentThread();
+ if (dispatcher is null)
+ {
+#if DEBUG
+ // Show when a dispatching was done or not
+ Debug.WriteLine($"{nameof(DispatchManager)}: Dispatcher => The `Dispatcher` property was null!");
+#endif
+ return;
+ }
+#if DEBUG
+ // Show when a dispatching was done or not
+ Debug.WriteLine($"{nameof(DispatchManager)}: Dispatcher => '{(dispatcher.IsDispatchRequired ? "dispatched" : "not dispatched")}'");
+ if (dispatcher.IsDispatchRequired)
+ {
+ // Just for a breaking point
+ }
+#endif
+ if (dispatcher.IsDispatchRequired || forceUiThread)
+ {
+ await dispatcher.DispatchAsync(() =>
+ {
+ try
+ {
+ action.Invoke();
+ }
+ catch (Exception exc)
+ {
+ OnError(new Events.DispatchErrorEventArgs(exc)
+ {
+ DispatchRequired = dispatcher.IsDispatchRequired
+ });
+ }
+ });
+ }
+ else
+ {
+ action.Invoke();
+ }
+ }
+ public Task DispatchAsync(Action action, bool forceUiThread = false)
+ => DispatchAsync(dispatcher: Dispatcher, action: action, forceUiThread: forceUiThread);
+
+ public async Task DispatchAsync(IDispatcher? dispatcher, Func action, bool forceUiThread = false)
+ {
+ dispatcher ??= DispatcherProvider.Current.GetForCurrentThread();
+ if (dispatcher is null)
+ {
+#if DEBUG
+ // Show when a dispatching was done or not
+ Debug.WriteLine($"{nameof(DispatchManager)}: Dispatcher => The `Dispatcher` property was null!");
+#endif
+ return;
+ }
+#if DEBUG
+ // Show when a dispatching was done or not
+ Debug.WriteLine($"{nameof(DispatchManager)}: Dispatcher => '{(dispatcher.IsDispatchRequired ? "dispatched" : "not dispatched")}'");
+ if (dispatcher.IsDispatchRequired)
+ {
+ // Just for a breaking point
+ }
+#endif
+ if (dispatcher.IsDispatchRequired || forceUiThread)
+ {
+ await dispatcher.DispatchAsync(async () =>
+ {
+ try
+ {
+ await action.Invoke();
+ }
+ catch (Exception exc)
+ {
+ OnError(new Events.DispatchErrorEventArgs(exc)
+ {
+ DispatchRequired = dispatcher.IsDispatchRequired
+ });
+ }
+ });
+ }
+ else
+ {
+ await action.Invoke();
+ }
+ }
+ public Task DispatchAsync(Func action, bool forceUiThread = false)
+ => DispatchAsync(dispatcher: Dispatcher, action: action, forceUiThread: forceUiThread);
+ #endregion
+
+ }
+}
diff --git a/src/SharedMauiCoreLibrary/Models/Events/DispatchErrorEventArgs.cs b/src/SharedMauiCoreLibrary/Models/Events/DispatchErrorEventArgs.cs
new file mode 100644
index 0000000..5f3de31
--- /dev/null
+++ b/src/SharedMauiCoreLibrary/Models/Events/DispatchErrorEventArgs.cs
@@ -0,0 +1,19 @@
+namespace AndreasReitberger.Shared.Core.Events
+{
+ public partial class DispatchErrorEventArgs : EventArgs
+ {
+ #region Properties
+ public bool DispatchRequired { get; set; }
+ public Exception? Exception { get; set; }
+
+ #endregion
+
+ #region Ctor
+ public DispatchErrorEventArgs() { }
+ public DispatchErrorEventArgs(Exception exception)
+ {
+ Exception = exception;
+ }
+ #endregion
+ }
+}
diff --git a/src/SharedMauiCoreLibrary/SharedMauiCoreLibrary.csproj b/src/SharedMauiCoreLibrary/SharedMauiCoreLibrary.csproj
index f87a37b..d1ac2d9 100644
--- a/src/SharedMauiCoreLibrary/SharedMauiCoreLibrary.csproj
+++ b/src/SharedMauiCoreLibrary/SharedMauiCoreLibrary.csproj
@@ -1,7 +1,7 @@
-
-
+
+
MAUI, Utilites, Core, Library, Helper, C#, dotnet