Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions src/SharedMauiCoreLibrary/Hosting/AppHostBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,9 +16,42 @@ namespace AndreasReitberger.Shared.Core.Hosting
[SupportedOSPlatform(SPlatforms.WindowsVersion)]
public static class AppHostBuilderExtensions
{
/// <summary>
/// Configures the core library features for a .NET MAUI application using the specified builder.
/// Included features:
/// - UseMauiCommunityToolkit()
/// - ConfigureDispatching()
/// </summary>
/// <remarks>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.</remarks>
/// <param name="builder">The <see cref="MauiAppBuilder"/> instance to configure. Cannot be null.</param>
/// <returns>The same <see cref="MauiAppBuilder"/> instance, enabling method chaining.</returns>
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<IDispatcher>();
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<IDispatchManager>(manager);
}
return builder;
}

Expand All @@ -25,7 +62,7 @@ public static MauiAppBuilder ConfigureLocalizationManager(this MauiAppBuilder bu
BaseFlagImageUri = flagImageBaseDir
};
manager.SetLanguages(languages);
builder.Services.AddSingleton<ILocalizationManager>(manager);
builder.Services.TryAddSingleton<ILocalizationManager>(manager);
return builder;
}

Expand All @@ -35,7 +72,7 @@ public static MauiAppBuilder ConfigureLocalizationManager(this MauiAppBuilder bu
{
BaseFlagImageUri = flagImageBaseDir
};
builder.Services.AddSingleton<ILocalizationManager>(manager);
builder.Services.TryAddSingleton<ILocalizationManager>(manager);
return builder;
}

Expand All @@ -44,7 +81,7 @@ public static MauiAppBuilder ConfigureShellNavigator(this MauiAppBuilder builder
dispatcher ??= builder.Services.BuildServiceProvider().GetService<IDispatcher>();
ShellNavigator navigator = dispatcher is not null ? new(rootPage, dispatcher) : new(rootPage);
navigator.AvailableEntryPages = [.. entryPages ?? [ rootPage]];
builder.Services.AddSingleton<IShellNavigator>(navigator);
builder.Services.TryAddSingleton<IShellNavigator>(navigator);
return builder;
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/SharedMauiCoreLibrary/Interfaces/IDispatchManager.cs
Original file line number Diff line number Diff line change
@@ -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<Task> action, bool forceUiThread = false);
public void Dispatch(Func<Task> 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<Task> action, bool forceUiThread = false);
public Task DispatchAsync(Func<Task> action, bool forceUiThread = false);
#endregion

#region Events
public event EventHandler? Error;
#endregion
}
}
Original file line number Diff line number Diff line change
@@ -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

}
}
233 changes: 233 additions & 0 deletions src/SharedMauiCoreLibrary/Models/Dispatch/DispatchManager.cs
Original file line number Diff line number Diff line change
@@ -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<Task> 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<Task> 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<Task> 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<Task> action, bool forceUiThread = false)
=> DispatchAsync(dispatcher: Dispatcher, action: action, forceUiThread: forceUiThread);
#endregion

}
}
19 changes: 19 additions & 0 deletions src/SharedMauiCoreLibrary/Models/Events/DispatchErrorEventArgs.cs
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading
Loading