Skip to content

bug(network): Android networkStatusChange re-queries the active network inside callbacks and fires on every capability change #2621

Open

Description

@kcjonson

Bug Report

Plugin(s)

@capacitor/network 8.0.1 (same code on main at 87c0bb8 and in 9.0.0-alpha.2)

Capacitor Version

@capacitor/core: 8.5.3
@capacitor/android: 8.5.3
@capacitor/network: 8.0.1
compileSdk 36, minSdk 24

Platform(s)

Android

Current Behavior

  1. The network callback ignores its arguments and re-queries the active network. onCapabilitiesChanged drops the NetworkCapabilities it receives (Network.java:29-32); NetworkPlugin then calls getNetworkStatus() (NetworkPlugin.java:84-86), which calls getActiveNetwork() twice and getNetworkCapabilities() (Network.java:77-78). The ConnectivityManager.NetworkCallback reference says not to call synchronous ConnectivityManager methods from inside callbacks, because the result can be out of date relative to the callback; the arguments passed in are the source of truth. onLost also emits none for any lost network without checking it was the current default (Network.java:22-26, NetworkPlugin.java:25-29).

  2. Every capability change emits an event, even when nothing reported changed. There is no comparison with the last emitted status (NetworkPlugin.java:84-86). onCapabilitiesChanged fires for signal strength and link bandwidth updates, so listeners get repeated networkStatusChange events with an identical { connected, connectionType }. Registering the callback on each resume also delivers an immediate callback, so every resume emits too. This was acknowledged as a bug in [Android] networkStatusChange-event fires when app becomes active capacitor#1492 (labeled type: bug, diagnosed as the onResume registration) and reported again in networkStatusChange event fires after on app resume #1014 and [Bug]: networkStatusChange is triggering the event when changing the wifi frequency #2138; all were closed in backlog cleanup without a fix.

  3. No events before the first resume. load() only sets the listener (NetworkPlugin.java:22-35); startMonitoring() is only called from handleOnResume (NetworkPlugin.java:59).

  4. Unguarded register/unregister. startMonitoring/stopMonitoring call registerDefaultNetworkCallback/unregisterNetworkCallback with no registered-state check (Network.java:114-123). Android throws IllegalArgumentException when unregistering a callback that isn't registered or registering one twice, so a pause without a preceding resume, or a repeated pause, can crash. Not reproduced; possibly related to @capacitor/network crashing on Android 14(SDK 34) for Samsung and Google pixel devices. #2107.

Expected Behavior

  • Status in callbacks comes from the callback's own Network/NetworkCapabilities.
  • networkStatusChange fires only when connected or connectionType actually changes.
  • Monitoring starts when the plugin loads.
  • Register/unregister can't throw on lifecycle ordering.

Code Reproduction

No sample app needed beyond the plugin's own example:

import { Network } from '@capacitor/network';
Network.addListener('networkStatusChange', s => console.log('event', Date.now(), JSON.stringify(s)));
  1. Run on a device connected to Wi-Fi.
  2. Walk away from the router and back (or move between rooms) so the signal level changes, without disconnecting.
  3. Logcat shows repeated networkStatusChange events, each with {"connected":true,"connectionType":"wifi"}.
  4. Background and foreground the app (open the notification shade, a permission dialog, or the app switcher): one more identical event per resume.

Other Technical Details

A PR follows. It keeps connected = VALIDATED && INTERNET and keeps getStatus() querying the active network as today. It doesn't change the public API.

Related open items, not duplicated by the PR: #2563 / #2606 (ANR from ConnectivityManager calls on the main thread in handleOnResume), #2564 (captive portal / limited state), #2555 (metered / constrained flags).

Additional Context

Prior reports: ionic-team/capacitor#1492, #1014, #2138, #2107.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions