plus_plugins icon indicating copy to clipboard operation
plus_plugins copied to clipboard

[Bug]: iOS Result.none after subscription cancel

Open alfie-trbc opened this issue 6 months ago • 1 comments

Platform

iOS 18.5

Plugin

connectivity_plus

Version

6.1.4

Flutter SDK

3.29.3

Steps to reproduce

  1. Have a listener on certain page and use "connectivity.onConnectivityChanged.listen".
  2. use subscription.cancel() when disposed.
  3. Go to other screen and trigger checkConnectivity()
  4. The initial result is ConnectivityResult.none
  5. And if I trigger again the checkConnectivity() the result is now okay.

Code Sample

// MAIN.DART
 
// ignore_for_file: public_member_api_docs
 
import 'dart:async';
import 'dart:developer' as developer;
 
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:connectivity_plus_example/connectivity_service.dart';
import 'package:flutter/material.dart';
 
import 'home_page.dart';
 
// Use the singleton connectivity instance
final Connectivity connectivity = ConnectivityService().connectivity;
 
void main() {
  // Add error logging to catch any async errors
  FlutterError.onError = (FlutterErrorDetails details) {
    FlutterError.presentError(details);
    developer.log('Flutter error caught: ${details.exception}',
        error: details.exception, stackTrace: details.stack);
  };
 
  runZonedGuarded(
    () => runApp(const MyApp()),
    (error, stack) {
      developer.log('Uncaught error in app:', error: error, stackTrace: stack);
    },
  );
}
 
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
 
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: const Color(0x9f4376f8),
      ),
      home: const MyHomePage(title: 'Connectivity Plus Example'),
    );
  }
}
 
//HOME PAGE

import 'dart:async';
import 'dart:developer' as developer;
 
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
 
import 'dummy_page.dart';
import 'main.dart';
 
class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
 
  final String title;
 
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
 
class _MyHomePageState extends State<MyHomePage> {
  List<ConnectivityResult> _connectionStatus = [ConnectivityResult.none];
 
  @override
  void initState() {
    super.initState();
    developer.log('HomePage: initState called');
 
    developer.log(
      'HomePage: initState called connectivity instance: ${connectivity.hashCode}',
    );
 
    initConnectivity();
 
    developer.log('HomePage: Setting up connectivity subscription');
  }
 
  @override
  void dispose() {
    developer.log('Subscription cancelled in HomePage');
    super.dispose();
  }
 
  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initConnectivity() async {
    late List<ConnectivityResult> result;
    // Platform messages may fail, so we use a try/catch PlatformException.
    try {
      result = await connectivity.checkConnectivity();
      developer.log('HomePage result: $result');
    } on PlatformException catch (e) {
      developer.log('Couldn\'t check connectivity status', error: e);
      return;
    }
 
    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) {
      return Future.value(null);
    }
 
    return _updateConnectionStatus(result);
  }
 
  Future<void> _updateConnectionStatus(List<ConnectivityResult> result) async {
    setState(() => _connectionStatus = result);
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        elevation: 4,
      ),
      body: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Spacer(flex: 2),
          Text(
            'Active connection types:',
            style: Theme.of(context).textTheme.headlineMedium,
          ),
          const Spacer(),
          ListView(
            shrinkWrap: true,
            children: List.generate(
                _connectionStatus.length,
                (index) => Center(
                      child: Text(
                        _connectionStatus[index].toString(),
                        style: Theme.of(context).textTheme.headlineSmall,
                      ),
                    )),
          ),
          const Spacer(),
          ElevatedButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const DummyPage(),
                ),
              );
            },
            child: const Text('Navigate to Dummy Page'),
          ),
          // TODO: await connectivity.checkConnectivity(); fixes the issue
          // ElevatedButton(
          //   onPressed: () async {
          //     // Force reinitialize the connectivity instance
          //     await connectivity.checkConnectivity();
          //     // Then do your actual check after it's reinitialized
          //     // await initConnectivity();
 
          //     developer.log(
          //       'status tapped: initState called connectivity instance: ${await connectivity.checkConnectivity()}',
          //     );
          //   },
          //   child: const Text('status'),
          // ),
          ElevatedButton(
            onPressed: () async {
              developer.log(
                'status tapped: initState called connectivity instance: ${connectivity.hashCode}',
              );
              await initConnectivity();
            },
            child: const Text('check connectivity'),
          ),
          const Spacer(flex: 2),
        ],
      ),
    );
  }
}
//Dummy Page
import 'dart:async';
import 'dart:developer' as developer;
 
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
 
import 'main.dart';
 
class DummyPage extends StatefulWidget {
  const DummyPage({Key? key}) : super(key: key);
 
  @override
  State<DummyPage> createState() => _DummyPageState();
}
 
class _DummyPageState extends State<DummyPage> {
  final bool _isListening = false;
  StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
 
  @override
  void initState() {
    super.initState();
    _startListening();
 
    developer.log(
        'DummyPage: initState called - using connectivity instance: ${connectivity.hashCode}');
  }
 
  @override
  void dispose() {
    developer.log(
        'Cancelling subscription in DummyPage with hashCode: ${_connectivitySubscription?.hashCode}');
    _connectivitySubscription?.cancel();
 
    developer.log(
        'DummyPage: dispose called - using connectivity instance: ${connectivity.hashCode}');
 
    super.dispose();
  }
 
  void _startListening() {
    if (!_isListening) {
      _connectivitySubscription =
          connectivity.onConnectivityChanged.listen((result) {
        developer.log(
          'listening: $result',
          name: 'DummyPage',
        );
      });
 
      developer.log(
          'startListening called in button press ${connectivity.hashCode}');
 
      setState(() {
        developer.log('setState called in DummyPage');
      });
    }
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Test Connectivity Subscription'),
        elevation: 4,
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Text(
              'Test if events are received after cancellation',
              style: Theme.of(context).textTheme.titleLarge,
            ),
          ),
          Column(
            children: [
              ElevatedButton(
                onPressed: () {
                  _startListening();
                },
                child: const Text('Start Listening'),
              ),
            ],
          ),
          const SizedBox(height: 16),
          Text(
            'Status: ${_isListening ? 'Listening' : 'Not listening'}',
            style: Theme.of(context).textTheme.titleMedium,
          ),
          const SizedBox(height: 16),
          const SizedBox(height: 16),
          Text(
            'Note: Change your device connectivity (WiFi/Mobile data) to trigger events',
            style: Theme.of(context).textTheme.bodySmall,
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }
}

//Singleton
import 'package:connectivity_plus/connectivity_plus.dart';
 
class ConnectivityService {
  // Singleton instance
  static final ConnectivityService _instance = ConnectivityService._internal();
 
  // Factory constructor to return the singleton instance
  factory ConnectivityService() => _instance;
 
  // Private constructor
  ConnectivityService._internal();
 
  // The single connectivity instance shared across the app
  final Connectivity connectivity = Connectivity();
}
 

Logs

none

Flutter Doctor

✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0) [2.5s]
    • Android SDK at /Users/user/Library/Android/sdk
    • Platform android-35, build-tools 35.0.0
    • ANDROID_HOME = /Users/user/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
      This is the JDK bundled with the latest Android Studio installation on this machine.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 21.0.6+-13368085-b895.109)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 16.4) [1,767ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 16F6
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web [11ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2024.3) [11ms]
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 21.0.6+-13368085-b895.109)

[✓] VS Code (version 1.100.3) [9ms]
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.112.0

[✓] Connected device (5 available) [6.5s]
    • sdk gphone64 arm64 (mobile)     • emulator-5554             • android-arm64  • Android 14 (API 34)
      (emulator)
    • iPhone (mobile)                 • 00008030-001C2C9A0ED2402E • ios            • iOS 18.5 22F76
    • macOS (desktop)                 • macos                     • darwin-arm64   • macOS 15.5 24F74
      darwin-arm64
    • Mac Designed for iPad (desktop) • mac-designed-for-ipad     • darwin         • macOS 15.5 24F74
      darwin-arm64
    • Chrome (web)                    • chrome                    • web-javascript • Google Chrome 137.0.7151.69

[✓] Network resources [533ms]
    • All expected network resources are available.

Checklist before submitting a bug

  • [x] I searched issues in this repository and couldn't find such bug/problem
  • [x] I Google'd a solution and I couldn't find it
  • [x] I searched on StackOverflow for a solution and I couldn't find it
  • [x] I read the README.md file of the plugin
  • [x] I'm using the latest version of the plugin
  • [x] All dependencies are up to date with flutter pub upgrade
  • [x] I did a flutter clean
  • [x] I tried running the example project

alfie-trbc avatar Jun 09 '25 06:06 alfie-trbc

Unfortunately, currently it is a normal behavior as mentioned in the README: https://github.com/fluttercommunity/plus_plugins/tree/main/packages/connectivity_plus/connectivity_plus#ios--macos

vbuberen avatar Jun 09 '25 17:06 vbuberen

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 15 days

github-actions[bot] avatar Sep 08 '25 00:09 github-actions[bot]