InstallPrompt
Capture the browser's beforeinstallprompt event, put the install button where it belongs in your own UI, and know when the app was actually installed.
@inject Bit.Butil.InstallPrompt installPromptMDN reference
beforeinstallprompt. On Safari and Firefox installing is a
manual browser-menu action, so IsSupported is false and there is nothing to prompt -
treat the install button as an enhancement, not a feature you can rely on.
installPrompt module is evaluated, which with
the classic bundle is early enough. Under UseLazyScripts the module is imported on
first use - possibly after the event has already fired - so add this to your host page and Butil
will adopt what it stashed:
<script>
window.addEventListener('beforeinstallprompt', e => {
e.preventDefault();
window.BitButilDeferredInstallPrompt = e;
});
</script>True when the runtime fires beforeinstallprompt at all. During prerender/SSR the check returns false rather than throwing, so defer it to OnAfterRenderAsync.
@inject Bit.Butil.InstallPrompt installPrompt
var supported = await installPrompt.IsSupported();IsStandalone is the portable check - it is true whenever the page is running as an installed app rather than in a tab, covering the standalone, minimal-ui, fullscreen and window-controls-overlay display modes plus iOS Safari's own flag. WasInstalled only reports an install that happened during this page's lifetime.
if (await installPrompt.IsStandalone())
{
// running as an installed app - hide the install button
}IsAvailable is the condition to show your install button on: true only once the browser has decided the app is installable and before the deferred prompt has been used. GetPlatforms reports what the captured event offers - ['web'], or ['play', 'web'] when the manifest names a related native app.
_canInstall = await installPrompt.IsAvailable();
var platforms = await installPrompt.GetPlatforms();Shows the deferred dialog and resolves with what the user chose. Must be called from a user gesture. The event is spent afterwards whatever the answer, so IsAvailable becomes false; a user who dismissed it can only be asked again after the browser fires beforeinstallprompt anew on a later visit.
Bit.Butil.InstallPrompt installPrompt
@* Must be a real user gesture: a prompt shown from a timer or from OnInitialized is refused. *@
<button @onclick="Install" disabled="@(_canInstall is false)">Install</button>
{
private bool _canInstall;
private async Task Install()
{
var result = await installPrompt.Prompt();
if (result.Accepted)
{
// installing on result.Platform
}
else if (result.Outcome == InstallPromptOutcome.Dismissed)
{
// asked and declined - don't nag
}
// The event is spent whatever the answer.
_canInstall = false;
}
}{
"name": "Bit.Butil demo",
"short_name": "Butil",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0f6cbd",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}<!-- No manifest, no beforeinstallprompt: the browser has nothing to install. Chromium also wants a
service worker with a fetch handler and an icon of at least 192x192 before it decides an app is
installable, which is why IsAvailable can stay false on a page that looks complete. -->
<link rel="manifest" href="manifest.json" />OnAvailable fires when a prompt becomes offerable - and immediately if one is already in hand, so subscribing late is safe. OnInstalled fires after the browser has installed the app: the moment to hide the button. Both return a ButilSubscription; dispose it in DisposeAsync.
_availableSub = await installPrompt.OnAvailable(platforms =>
{
_canInstall = true;
InvokeAsync(StateHasChanged);
});
_installedSub = await installPrompt.OnInstalled(() =>
{
_canInstall = false;
InvokeAsync(StateHasChanged);
});API reference
ValueTask<bool> IsSupported()ValueTask<bool> IsAvailable()ValueTask<string[]> GetPlatforms()ValueTask<bool> WasInstalled()ValueTask<bool> IsStandalone()ValueTask<InstallPromptResult> Prompt()Task<ButilSubscription> OnAvailable(Action<string[]> handler)Task<ButilSubscription> OnInstalled(Action handler)ValueTask DisposeAsync()