Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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: 47 additions & 0 deletions src/BlazorWebView/src/Maui/Android/BlazorWebViewHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.Maui;
using Microsoft.Maui.Dispatching;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.LifecycleEvents;
using static global::Android.Views.ViewGroup;
using AWebView = global::Android.Webkit.WebView;
using Path = System.IO.Path;
Expand All @@ -21,6 +22,7 @@ public partial class BlazorWebViewHandler : ViewHandler<IBlazorWebView, AWebView
private WebChromeClient? _webChromeClient;
private AndroidWebKitWebViewManager? _webviewManager;
internal AndroidWebKitWebViewManager? WebviewManager => _webviewManager;
private AndroidLifecycle.OnBackPressed? _onBackPressedHandler;

private ILogger? _logger;
internal ILogger Logger => _logger ??= Services!.GetService<ILogger<BlazorWebViewHandler>>() ?? NullLogger<BlazorWebViewHandler>.Instance;
Expand Down Expand Up @@ -60,10 +62,55 @@ protected override AWebView CreatePlatformView()
return blazorAndroidWebView;
}

protected override void ConnectHandler(AWebView platformView)
{
base.ConnectHandler(platformView);

// Register OnBackPressed lifecycle event handler to check WebView's back navigation
// This ensures predictive back gesture (Android 13+) checks WebView.CanGoBack() before popping page
var services = MauiContext?.Services;
if (services != null)
{
// Create a weak reference to avoid memory leaks
var weakPlatformView = new WeakReference<AWebView>(platformView);

AndroidLifecycle.OnBackPressed handler = (activity) =>
{
// Check if WebView is still alive and can navigate back
if (weakPlatformView.TryGetTarget(out var webView) && webView.CanGoBack())
{
webView.GoBack();
return true; // Prevent back propagation - handled by WebView
}

return false; // Allow back propagation - let page be popped
};

// Register with lifecycle service - will be invoked by HandleBackNavigation in MauiAppCompatActivity
var lifecycleService = services.GetService<ILifecycleEventService>();
if (lifecycleService is LifecycleEventService concreteService)
{
concreteService.AddEvent(nameof(AndroidLifecycle.OnBackPressed), handler);
_onBackPressedHandler = handler;
}
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code casts ILifecycleEventService to LifecycleEventService to call AddEvent, but RemoveEvent is also internal and requires the same cast. Consider either: (1) Making RemoveEvent part of ILifecycleEventService or ILifecycleBuilder interface for better API design, or (2) Adding a comment explaining why this internal cast is necessary. The current approach creates coupling to the concrete implementation.

Copilot uses AI. Check for mistakes.
}
}

private const string AndroidFireAndForgetAsyncSwitch = "BlazorWebView.AndroidFireAndForgetAsync";

protected override void DisconnectHandler(AWebView platformView)
{
// Clean up lifecycle event handler to prevent memory leaks
if (_onBackPressedHandler != null && MauiContext?.Services != null)
{
var lifecycleService = MauiContext.Services.GetService<ILifecycleEventService>();
if (lifecycleService is LifecycleEventService concreteService)
{
concreteService.RemoveEvent(nameof(AndroidLifecycle.OnBackPressed), _onBackPressedHandler);
_onBackPressedHandler = null;
}
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same cast pattern (ILifecycleEventService to LifecycleEventService) is repeated in both ConnectHandler and DisconnectHandler. Consider extracting this to a helper method or property to reduce code duplication and make the intent clearer. For example, a private method like TryGetLifecycleEventService() that returns the concrete service or null.

Copilot uses AI. Check for mistakes.
}
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compound null check can be simplified for better readability. Consider checking MauiContext?.Services first and storing it in a local variable, then checking _onBackPressedHandler separately. This makes the code flow clearer and avoids repeated null-conditional access.

Copilot uses AI. Check for mistakes.

platformView.StopLoading();

if (_webviewManager != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
override Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebViewHandler.ConnectHandler(Android.Webkit.WebView! platformView) -> void
9 changes: 9 additions & 0 deletions src/Core/src/LifecycleEvents/LifecycleEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,14 @@ public IEnumerable<TDelegate> GetEventDelegates<TDelegate>(string eventName)

public bool ContainsEvent(string eventName) =>
_mapper.TryGetValue(eventName, out var delegates) && delegates?.Count > 0;

internal void RemoveEvent<TDelegate>(string eventName, TDelegate action)
where TDelegate : Delegate
{
if (_mapper.TryGetValue(eventName, out var delegates) && delegates != null)
{
delegates.Remove(action);
}
}
}
}
Loading