Skip to content

[Bug]: iOS Result.none after subscription cancel #3594

Open
@alfie-trbc

Description

@alfie-trbc

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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions