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
2 changes: 1 addition & 1 deletion sample/Maui.Android.InAppUpdates.SampleApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.7" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
</PropertyGroup>

<PropertyGroup Label="NuGet">
<PackageId>Oscore.$(AssemblyName)</PackageId>
<Description>NuGet package that implementing Android In-App Updates for MAUI with debugging capabilities.</Description>
<PackageTags>maui;android;in-app-updates;updates;in-app;net8;dotnet;csharp</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net9.0-android'" Label="Android In-app Updates">
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.10.1.1" />
<PackageReference Include="Xamarin.AndroidX.Collection.Ktx" Version="1.5.0.1" />
<PackageReference Include="Xamarin.AndroidX.Fragment.Ktx" Version="1.8.6.1" />
<PackageReference Include="Xamarin.Google.Android.Play.App.Update.Ktx" Version="2.1.0.14" />
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.10.1.2" />
<PackageReference Include="Xamarin.AndroidX.Collection.Ktx" Version="1.5.0.2" />
<PackageReference Include="Xamarin.AndroidX.Fragment.Ktx" Version="1.8.8" />
<PackageReference Include="Xamarin.Google.Android.Play.App.Update.Ktx" Version="2.1.0.15" />
</ItemGroup>

<ItemGroup Condition="$(TargetFramework.StartsWith('Xamarin.iOS')) != true AND $(TargetFramework.StartsWith('net9.0-ios')) != true AND $(TargetFramework.StartsWith('net9.0-maccatalyst')) != true ">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,30 @@ public static class DefaultUserInterface
/// Displays a short duration toast message at the center of the screen.
/// </summary>
/// <param name="text">The text to be displayed in the toast message.</param>
public static void ShowShortToast(
string text)
public static void ShowShortToast(string text)
{
Toast.MakeText(
context: Platform.AppContext,
text: text,
duration: ToastLength.Short)?.Show();
try
{
if (Platform.AppContext is null)
{
Handler.Options.DebugAction($"Cannot show toast - Platform.AppContext is null: {text}");
return;
}

Toast.MakeText(
context: Platform.AppContext,
text: text,
duration: ToastLength.Short)?.Show();
}
catch (Exception ex)
{
Handler.Options.DebugAction($"Failed to show toast '{text}': {ex}");
}
}

/// <summary>
/// Displays a snackbar with an action to complete the app update process.
/// If snackbar fails due to theme incompatibility, falls back to toast message.
/// </summary>
/// <param name="text">The text to display on the snackbar.</param>
/// <param name="actionText">The text for the action button.</param>
Expand All @@ -30,18 +43,86 @@ public static void ShowSnackbar(
string actionText,
Action<global::Android.Views.View> clickHandler)
{
if (Platform.CurrentActivity?.Window?.DecorView is not {} view ||
Snackbar.Make(
text: text,
duration: BaseTransientBottomBar.LengthIndefinite,
view: view) is not {} snackbar)
var fallbackMessage = $"{text} - {actionText}";

try
{
var view = Platform.CurrentActivity?.Window?.DecorView;
if (view is null)
{
FallbackToToastWithAction("Cannot show snackbar - no active view", fallbackMessage, clickHandler, null);
return;
}

var snackbar = Snackbar.Make(view, text, BaseTransientBottomBar.LengthIndefinite);
if (snackbar is null)
{
FallbackToToastWithAction("Cannot create snackbar", fallbackMessage, clickHandler, view);
return;
}

snackbar.SetAction(text: actionText, clickHandler: clickHandler);
snackbar.Show();
}
catch (Exception ex) when (IsThemeRelated(ex))
{
HandleSnackbarFailure(ex, "theme related", fallbackMessage, clickHandler, Platform.CurrentActivity?.Window?.DecorView);
}
catch (Exception ex)
{
return;
HandleSnackbarFailure(ex, "general exception", fallbackMessage, null, null);
}

snackbar.SetAction(
text: actionText,
clickHandler: clickHandler);
snackbar.Show();
}

private static void FallbackToToastWithAction(
string debugMessage,
string fallbackMessage,
Action<global::Android.Views.View>? clickHandler,
global::Android.Views.View? fallbackView)
{
Handler.Options.DebugAction($"{debugMessage}, falling back to toast and auto-triggering action");
ShowShortToast(fallbackMessage);

// Auto-trigger the action since user can't click the snackbar
if (clickHandler is not null && fallbackView is not null)
{
try
{
clickHandler(fallbackView);
}
catch (Exception actionEx)
{
Handler.Options.DebugAction($"Error auto-executing snackbar action: {actionEx}");
}
}
}

private static void HandleSnackbarFailure(
Exception ex,
string errorType,
string fallbackMessage,
Action<global::Android.Views.View>? clickHandler,
global::Android.Views.View? view)
{
Handler.Options.DebugAction($"Snackbar {errorType}: {ex}");
ShowShortToast(fallbackMessage);

// Only auto-trigger for theme-related exceptions where user interaction was expected
if (clickHandler is not null && view is not null)
{
try
{
clickHandler(view);
}
catch (Exception actionEx)
{
Handler.Options.DebugAction($"Error executing snackbar action: {actionEx}");
}
}
}

private static bool IsThemeRelated(Exception ex) =>
ex is global::Android.Views.InflateException ||
(ex is Java.Lang.UnsupportedOperationException &&
ex.Message?.Contains("Failed to resolve attribute", StringComparison.Ordinal) == true);
}