Skip to content

Commit 5346aed

Browse files
committed
Add .NET MAUI sample app
1 parent 5eb567a commit 5346aed

23 files changed

Lines changed: 548 additions & 0 deletions

Exceptionless.Net.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<Project Path="samples/Exceptionless.SampleHosting/Exceptionless.SampleHosting.csproj" />
2020
<Project Path="samples/Exceptionless.SampleLambda/Exceptionless.SampleLambda.csproj" />
2121
<Project Path="samples/Exceptionless.SampleLambdaAspNetCore/Exceptionless.SampleLambdaAspNetCore.csproj" />
22+
<Project Path="samples/Exceptionless.SampleMaui/Exceptionless.SampleMaui.csproj" />
2223
<Project Path="samples/Exceptionless.SampleMvc/Exceptionless.SampleMvc.csproj">
2324
<BuildDependency Project="src/Exceptionless/Exceptionless.csproj" />
2425
<BuildDependency Project="src/Platforms/Exceptionless.Mvc/Exceptionless.Mvc.csproj" />
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace Exceptionless.SampleMaui;
2+
3+
public sealed class App : Application {
4+
private readonly ExceptionlessClient _exceptionlessClient;
5+
private readonly MainPage _mainPage;
6+
7+
public App(MainPage mainPage, ExceptionlessClient exceptionlessClient) {
8+
_exceptionlessClient = exceptionlessClient;
9+
_mainPage = mainPage;
10+
}
11+
12+
protected override Window CreateWindow(IActivationState? activationState) {
13+
return new Window(_mainPage);
14+
}
15+
16+
protected override void OnSleep() {
17+
_ = _exceptionlessClient.ProcessQueueAsync();
18+
base.OnSleep();
19+
}
20+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net10.0-android</TargetFrameworks>
5+
<TargetFrameworks Condition="!$([MSBuild]::IsOSPlatform('linux'))">$(TargetFrameworks);net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
6+
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>
7+
<OutputType>Exe</OutputType>
8+
<RootNamespace>Exceptionless.SampleMaui</RootNamespace>
9+
<UseMaui>true</UseMaui>
10+
<SingleProject>true</SingleProject>
11+
<ImplicitUsings>enable</ImplicitUsings>
12+
<Nullable>enable</Nullable>
13+
14+
<ApplicationTitle>Exceptionless MAUI Sample</ApplicationTitle>
15+
<ApplicationId>com.exceptionless.samplemaui</ApplicationId>
16+
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
17+
<ApplicationVersion>1</ApplicationVersion>
18+
<WindowsPackageType>None</WindowsPackageType>
19+
20+
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
21+
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
22+
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
23+
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
24+
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
25+
</PropertyGroup>
26+
27+
<ItemGroup>
28+
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#3A6EA5" />
29+
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#3A6EA5" BaseSize="128,128" />
30+
</ItemGroup>
31+
32+
<ItemGroup>
33+
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
34+
</ItemGroup>
35+
36+
<ItemGroup>
37+
<ProjectReference Include="..\..\src\Exceptionless\Exceptionless.csproj" SetTargetFramework="TargetFramework=netstandard2.0" />
38+
</ItemGroup>
39+
40+
</Project>
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
using Exceptionless.Logging;
2+
using Microsoft.Maui.Controls.Shapes;
3+
4+
namespace Exceptionless.SampleMaui;
5+
6+
public sealed class MainPage : ContentPage {
7+
private readonly ExceptionlessClient _exceptionlessClient;
8+
private readonly Label _statusLabel;
9+
private readonly Label _lastReferenceIdLabel;
10+
private readonly ActivityIndicator _activityIndicator;
11+
12+
public MainPage(ExceptionlessClient exceptionlessClient) {
13+
_exceptionlessClient = exceptionlessClient;
14+
15+
Title = "Exceptionless";
16+
BackgroundColor = Color.FromArgb("#F6F8FA");
17+
18+
_statusLabel = new Label {
19+
Text = "Ready",
20+
FontSize = 14,
21+
TextColor = Color.FromArgb("#314256"),
22+
LineBreakMode = LineBreakMode.WordWrap
23+
};
24+
25+
_lastReferenceIdLabel = new Label {
26+
Text = "Last reference id: none",
27+
FontSize = 13,
28+
TextColor = Color.FromArgb("#576575"),
29+
LineBreakMode = LineBreakMode.TailTruncation
30+
};
31+
32+
_activityIndicator = new ActivityIndicator {
33+
IsVisible = false,
34+
Color = Color.FromArgb("#276749")
35+
};
36+
37+
Content = BuildContent();
38+
}
39+
40+
private View BuildContent() {
41+
var sendExceptionButton = CreateActionButton("Send Handled Exception", OnSendExceptionClicked);
42+
var sendLogButton = CreateActionButton("Send Warning Log", OnSendLogClicked);
43+
var trackFeatureButton = CreateActionButton("Track Feature", OnTrackFeatureClicked);
44+
var flushButton = CreateActionButton("Flush Queue", OnFlushClicked);
45+
46+
return new ScrollView {
47+
Content = new VerticalStackLayout {
48+
Padding = new Thickness(24, 28),
49+
Spacing = 18,
50+
Children = {
51+
new Label {
52+
Text = "Exceptionless MAUI Sample",
53+
FontSize = 26,
54+
FontAttributes = FontAttributes.Bold,
55+
TextColor = Color.FromArgb("#1D2733")
56+
},
57+
new Label {
58+
Text = "Submit sample events through the core Exceptionless client.",
59+
FontSize = 15,
60+
TextColor = Color.FromArgb("#576575"),
61+
LineBreakMode = LineBreakMode.WordWrap
62+
},
63+
new Border {
64+
Stroke = Color.FromArgb("#D8DEE6"),
65+
StrokeThickness = 1,
66+
BackgroundColor = Colors.White,
67+
StrokeShape = new RoundRectangle { CornerRadius = 8 },
68+
Padding = new Thickness(18),
69+
Content = new VerticalStackLayout {
70+
Spacing = 12,
71+
Children = {
72+
new Label {
73+
Text = "Client",
74+
FontSize = 18,
75+
FontAttributes = FontAttributes.Bold,
76+
TextColor = Color.FromArgb("#1D2733")
77+
},
78+
new Label {
79+
Text = $"Server: {_exceptionlessClient.Configuration.ServerUrl}",
80+
FontSize = 13,
81+
TextColor = Color.FromArgb("#576575"),
82+
LineBreakMode = LineBreakMode.TailTruncation
83+
},
84+
new Label {
85+
Text = $"Private information: {_exceptionlessClient.Configuration.IncludePrivateInformation}",
86+
FontSize = 13,
87+
TextColor = Color.FromArgb("#576575")
88+
},
89+
_statusLabel,
90+
_lastReferenceIdLabel,
91+
_activityIndicator
92+
}
93+
}
94+
},
95+
new Grid {
96+
ColumnDefinitions = {
97+
new ColumnDefinition { Width = GridLength.Star },
98+
new ColumnDefinition { Width = GridLength.Star }
99+
},
100+
RowDefinitions = {
101+
new RowDefinition { Height = GridLength.Auto },
102+
new RowDefinition { Height = GridLength.Auto }
103+
},
104+
ColumnSpacing = 12,
105+
RowSpacing = 12,
106+
Children = {
107+
sendExceptionButton,
108+
sendLogButton,
109+
trackFeatureButton,
110+
flushButton
111+
}
112+
}
113+
}
114+
}
115+
};
116+
}
117+
118+
private static Button CreateActionButton(string text, EventHandler clicked) {
119+
var button = new Button {
120+
Text = text,
121+
BackgroundColor = Color.FromArgb("#285A84"),
122+
TextColor = Colors.White,
123+
CornerRadius = 8,
124+
FontAttributes = FontAttributes.Bold,
125+
MinimumHeightRequest = 48
126+
};
127+
128+
button.Clicked += clicked;
129+
return button;
130+
}
131+
132+
private async void OnSendExceptionClicked(object? sender, EventArgs e) {
133+
await RunClientActionAsync("Handled exception queued.", () => {
134+
string referenceId = Guid.NewGuid().ToString("N");
135+
136+
try {
137+
throw new InvalidOperationException("Exceptionless MAUI sample handled exception.");
138+
} catch (Exception ex) {
139+
ex.ToExceptionless(_exceptionlessClient)
140+
.SetReferenceId(referenceId)
141+
.AddTags("handled")
142+
.SetProperty("Screen", nameof(MainPage))
143+
.Submit();
144+
}
145+
146+
SetLastReferenceId(referenceId);
147+
return Task.CompletedTask;
148+
});
149+
}
150+
151+
private async void OnSendLogClicked(object? sender, EventArgs e) {
152+
await RunClientActionAsync("Warning log queued.", () => {
153+
_exceptionlessClient.SubmitLog("Exceptionless.SampleMaui.MainPage", "MAUI sample warning log.", LogLevel.Warn);
154+
SetLastReferenceId(_exceptionlessClient.GetLastReferenceId());
155+
return Task.CompletedTask;
156+
});
157+
}
158+
159+
private async void OnTrackFeatureClicked(object? sender, EventArgs e) {
160+
await RunClientActionAsync("Feature usage queued.", () => {
161+
_exceptionlessClient.SubmitFeatureUsage("MauiSample.TrackFeature");
162+
SetLastReferenceId(_exceptionlessClient.GetLastReferenceId());
163+
return Task.CompletedTask;
164+
});
165+
}
166+
167+
private async void OnFlushClicked(object? sender, EventArgs e) {
168+
await RunClientActionAsync("Queue processed.", () => _exceptionlessClient.ProcessQueueAsync());
169+
}
170+
171+
private async Task RunClientActionAsync(string successMessage, Func<Task> action) {
172+
try {
173+
_activityIndicator.IsVisible = true;
174+
_activityIndicator.IsRunning = true;
175+
_statusLabel.Text = "Working...";
176+
177+
await action();
178+
179+
_statusLabel.Text = successMessage;
180+
} catch (Exception ex) {
181+
_statusLabel.Text = $"Error: {ex.Message}";
182+
} finally {
183+
_activityIndicator.IsRunning = false;
184+
_activityIndicator.IsVisible = false;
185+
}
186+
}
187+
188+
private void SetLastReferenceId(string? referenceId) {
189+
_lastReferenceIdLabel.Text = String.IsNullOrEmpty(referenceId)
190+
? "Last reference id: none"
191+
: $"Last reference id: {referenceId}";
192+
}
193+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Exceptionless.Logging;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Maui.Storage;
4+
5+
namespace Exceptionless.SampleMaui;
6+
7+
public static class MauiProgram {
8+
private const string DefaultApiKey = "LhhP1C9gijpSKCslHHCvwdSIz298twx271nTest";
9+
private const string DefaultServerUrl = "https://ex.dev.localhost:7111";
10+
11+
public static MauiApp CreateMauiApp() {
12+
var builder = MauiApp.CreateBuilder();
13+
var exceptionlessClient = CreateExceptionlessClient();
14+
15+
builder
16+
.UseMauiApp<App>();
17+
18+
builder.Services.AddSingleton(exceptionlessClient);
19+
builder.Services.AddSingleton<MainPage>();
20+
21+
return builder.Build();
22+
}
23+
24+
private static ExceptionlessClient CreateExceptionlessClient() {
25+
string appDataDirectory = FileSystem.Current.AppDataDirectory;
26+
27+
var client = new ExceptionlessClient(config => {
28+
config.ApiKey = Environment.GetEnvironmentVariable("EXCEPTIONLESS_API_KEY") ?? DefaultApiKey;
29+
config.ServerUrl = Environment.GetEnvironmentVariable("EXCEPTIONLESS_SERVER_URL") ?? DefaultServerUrl;
30+
config.IncludePrivateInformation = false;
31+
config.DefaultTags.Add("maui");
32+
config.DefaultTags.Add("sample");
33+
config.DefaultData["Platform"] = DeviceInfo.Current.Platform.ToString();
34+
config.DefaultData["DeviceIdiom"] = DeviceInfo.Current.Idiom.ToString();
35+
config.SetVersion(AppInfo.Current.VersionString);
36+
config.UseFolderStorage(Path.Combine(appDataDirectory, "exceptionless-queue"));
37+
config.UseFileLogger(Path.Combine(appDataDirectory, "exceptionless-client.log"), LogLevel.Info);
38+
});
39+
40+
client.Startup();
41+
return client;
42+
}
43+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true" />
4+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
5+
<uses-permission android:name="android.permission.INTERNET" />
6+
</manifest>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using Android.App;
2+
using Android.Content.PM;
3+
4+
namespace Exceptionless.SampleMaui;
5+
6+
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
7+
public class MainActivity : MauiAppCompatActivity {
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Android.App;
2+
using Android.Runtime;
3+
4+
namespace Exceptionless.SampleMaui;
5+
6+
[Application]
7+
public class MainApplication : MauiApplication {
8+
public MainApplication(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership) {
9+
}
10+
11+
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using Foundation;
2+
3+
namespace Exceptionless.SampleMaui;
4+
5+
[Register("AppDelegate")]
6+
public class AppDelegate : MauiUIApplicationDelegate {
7+
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.security.app-sandbox</key>
6+
<true/>
7+
<key>com.apple.security.network.client</key>
8+
<true/>
9+
</dict>
10+
</plist>

0 commit comments

Comments
 (0)