Skip to content

Navigate and reload the frontend in place for the webview command#7163

Draft
markfrancisonly wants to merge 3 commits into
home-assistant:mainfrom
markfrancisonly:fix/command-webview-navigate-bus
Draft

Navigate and reload the frontend in place for the webview command#7163
markfrancisonly wants to merge 3 commits into
home-assistant:mainfrom
markfrancisonly:fix/command-webview-navigate-bus

Conversation

@markfrancisonly

@markfrancisonly markfrancisonly commented Jul 11, 2026

Copy link
Copy Markdown

Summary

command_webview relaunched the whole app for every request, even when the frontend was already open and showing the targeted server. Each launch created a new task (FLAG_ACTIVITY_MULTIPLE_TASK), so repeated commands piled up activity instances, and the page state — camera streams, scroll position, dialogs — was lost every time.

The command now acts on the open frontend in place:

  • a relative path is sent over the external bus using the navigate command (Home Assistant 2025.6+),
  • an entityId: target opens the more-info dialog (the prefix is matched ignoring case and surrounding spaces, since the value is typed by hand),
  • a new reload value reloads the frontend without cached data, so the page releases the resources it holds, like camera streams and WebRTC connections.

When the frontend is not visible — or the value is something the frontend cannot show, like an absolute URL — the app is launched as before, now reusing the existing task (FLAG_ACTIVITY_CLEAR_TOP instead of FLAG_ACTIVITY_MULTIPLE_TASK) so repeated commands can no longer accumulate task instances.

The visible frontend publishes its server through a small mediator while resumed; publication happens on resumed rather than started so that, during instance transitions, a stopping instance can never clear the value the foreground instance just published. In-place delivery is gated on the external bus being connected: the legacy WebView falls back to a full load while the page is still booting, and the v2 frontend holds the latest request until the frontend handshake completes.

Fixes #5381

Checklist

  • New or updated tests have been added to cover the changes following the testing guidelines.
  • The code follows the project's code style and best_practices.
  • The changes have been thoroughly tested, and edge cases have been considered.
  • Changes are backward compatible whenever feasible. Any breaking changes are documented in the changelog for users and/or in the code for developers depending on the relevance.

Link to pull request in documentation repositories

User Documentation: home-assistant/companion.home-assistant#1375

Any other notes

Wired for both the legacy WebViewActivity and the USE_FRONTEND_V2 path. Verified on-device (Samsung SM-X110): dashboard-to-dashboard navigation, more-info for entityId: targets, and reload all act on the open frontend with no relaunch; commands received while the app is closed launch a single task. Servers older than 2025.6 keep the previous launch behavior.

The webview command relaunched the whole app for every request, even
when the frontend was already open, piling up tasks and dropping the
state of the page. It now navigates the open frontend in place over
the external bus, opens the more info dialog for entityId: targets,
and supports reload as its command to reload the frontend without
cached data so the page releases the resources it holds, like camera
streams and WebRTC connections. When the frontend is not visible, the
app is launched like before, reusing the existing task instead of
spawning a new one every time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 11, 2026 17:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the command_webview notification command so that, when the Home Assistant frontend is already open on the targeted server, the command navigates/reloads in-place instead of relaunching the app (preserving page state and avoiding accumulating task instances). It introduces small mediator singletons to route “navigate” / “reload” requests to the visible frontend, with fallbacks to launching the app when in-place delivery is not possible.

Changes:

  • Route command_webview to in-place navigation (relative path / entityId: more-info) via the external bus when the frontend is visible and supported
  • Add an in-place reload option (hard refresh: clears WebView cache then reloads) and stop creating multiple tasks on repeated launches
  • Add unit/Robolectric/Compose tests covering mediators, parsing, lifecycle visibility, and command routing

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt Routes command_webview to in-place navigation/reload when possible; changes relaunch flags to reuse existing task
app/src/main/kotlin/io/homeassistant/companion/android/util/WebViewNavigationMediator.kt New singleton mediator for publishing visible server + emitting navigation requests to the visible frontend
app/src/main/kotlin/io/homeassistant/companion/android/util/ReloadRequestMediator.kt New singleton mediator for emitting “reload” requests to the visible frontend
app/src/main/kotlin/io/homeassistant/companion/android/frontend/navigation/FrontendTarget.kt Improves parsing of entityId: targets (case/whitespace tolerant)
app/src/main/kotlin/io/homeassistant/companion/android/frontend/FrontendViewModel.kt Consumes navigation/reload mediator events and forwards them to WebView actions / external bus (frontend v2 path)
app/src/main/kotlin/io/homeassistant/companion/android/frontend/FrontendScreen.kt Publishes frontend visibility via a lifecycle effect so commands can target the currently visible frontend
app/src/main/kotlin/io/homeassistant/companion/android/frontend/WebViewAction.kt Adds HardReload WebView action (clear cache + reload)
app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt Handles mediator events for legacy WebViewActivity path (reload + navigation delivery)
app/src/main/res/xml/changelog_master.xml Adds changelog entry for improved command_webview behavior (Main + Automotive)
app/src/test/kotlin/io/homeassistant/companion/android/util/WebViewNavigationMediatorTest.kt New unit tests for navigation mediator behavior
app/src/test/kotlin/io/homeassistant/companion/android/notifications/MessagingManagerWebViewCommandTest.kt New Robolectric tests for command_webview routing (navigate/entity/reload/fallback cases)
app/src/test/kotlin/io/homeassistant/companion/android/frontend/WebViewActionTest.kt Adds test for HardReload ordering (clear cache before reload)
app/src/test/kotlin/io/homeassistant/companion/android/frontend/navigation/FrontendTargetTest.kt Adds parameterized tests for hand-typed entityId: parsing + prefix edge case
app/src/test/kotlin/io/homeassistant/companion/android/frontend/FrontendVisibleLifecycleEffectTest.kt New Compose test validating visibility publication across lifecycle stop/start
app/src/test/kotlin/io/homeassistant/companion/android/frontend/FrontendViewModelTest.kt Adds tests for reload/nav mediator integration and visible-server publication behavior

…ntend

The command target is now parsed trimmed so hand-typed spaces or scheme
casing cannot slip an absolute URL or launch prefix past the in-place
navigation exclusions, and the entity id is percent-encoded in the
more-info URL fallback. UrlUtil.isAbsoluteUrl matches the scheme
ignoring case.

On the v2 frontend, a reload only drops the cache once connected since
doing so mid-load can wedge the page, and navigation requests wait for
the frontend handshake (keeping only the latest) because bus messages
sent before it are lost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@markfrancisonly

markfrancisonly commented Jul 11, 2026

Copy link
Copy Markdown
Author

prior versions of this command accumulated in system resource exhaustion and additionally resulted in delayed nav. this version has good manners and only starts/restarts the ha companion app if closed. to compensate and complete the journey, there's a now reload command that performs a webview hard refresh or loads the app fresh

@TimoPtr TimoPtr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would prefer using Intent to also allow opening a link without reopening the LaunchActivity and it would drop the need of this two mediators. The WebViewActivity has been removed since this morning.


fun isAbsoluteUrl(it: String?): Boolean {
return Regex("^https?://").containsMatchIn(it.toString())
return Regex("^https?://", RegexOption.IGNORE_CASE).containsMatchIn(it.toString())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This could be in its own PR

val keepScreenOnEnabled by viewModel.keepScreenOnEnabled.collectAsStateWithLifecycle()
val improvScanRequested by viewModel.improvScanRequested.collectAsStateWithLifecycle()

FrontendVisibleLifecycleEffect(viewModel::setFrontendVisible)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Look at the pattern in this file we already have a Effect high level composable where to put such Effect.

// Dropping the cache while the page is still loading can wedge the load
_webViewActions.emit(
if (_viewState.value is FrontendViewState.Content) {
WebViewAction.HardReload()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why doing a hard reload?

WebViewAction.OpenMoreInfo(target.entityId),
)
is FrontendTarget.Path -> externalBusRepository.send(NavigateToMessage(path = target.path))
FrontendTarget.Default -> externalBusRepository.send(NavigateToMessage(path = "/"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why not using navigateToDefaultDashboard?

}

viewModelScope.launch {
// Requests are only made for servers supporting navigate, no version check is needed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You are assuming that the mediator is always going to gate the version, but it might change in the future.

// Requests are only made for servers supporting navigate, no version check is needed.
// Bus messages sent before the frontend handshake are lost, so each request waits for
// it and only the latest request is kept while waiting
webViewNavigationMediator.navigationRequests.collectLatest { target ->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I get your idea here, but I think to be more generic we should not rely on an in memory flow that notify this VM. Instead I would prefer to use a deep link and not restarting the LaunchActivity, and having the notification command open the deep link. This way it is more flexible, to do that we need to handle on the LaunchActivity new intent coming in, I didn't check how feasible it is.

* Carries webview navigation requests from the server commands to the visible frontend.
*/
@Singleton
class WebViewNavigationMediator @Inject constructor() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you really need to know if the frontend is visible??

@home-assistant

Copy link
Copy Markdown

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@home-assistant home-assistant Bot marked this pull request as draft July 13, 2026 11:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

command_webview should not relaunch application

3 participants