Flutter 三方库 connectivity_plus 的鸿蒙化适配指南:网络状态实时监听,离线提示贴心守护,让你的应用更懂网络

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

前言:网络是个"小妖精",需要好好"伺候"

嗨,亲爱的小伙伴们!今天要和大家分享一个超超超实用的话题——如何在 Flutter for OpenHarmony 应用中实现网络状态的实时监听、友好的离线提示,以及酷炫的网络恢复自动刷新功能!✨

你有没有遇到过这样的场景呀?用户在地铁上刷着你的应用,突然进入隧道没网了,结果应用还在那傻傻地转圈圈,半天之后才弹出一个冷冰冰的"加载失败";或者用户从无网环境回到有网环境,应用却不知道自动刷新数据,还要用户手动去点刷新按钮——这种体验,真的会让用户好感度直接归零呢!😱

别担心!connectivity_plus 库来啦!它就像一位贴心的"网络管家",时刻关注着设备的网络状态变化,一旦发现网络"离家出走",立刻温柔地提醒用户;等网络"回家"了,又会自动帮你把数据刷新好——简直是完美的小助手!💕

特别是在鸿蒙生态蓬勃发展的今天,掌握网络状态监听能力,已经成为 Flutter 开发者必备的技能之一。接下来,就让博主手把手教你们玩转这个强大的库吧!准备好了吗?Let’s go!🚀

一、为什么 connectivity_plus 是鸿蒙开发者的"网络守护神"?

1.1 认识这位"网络小天使"

connectivity_plus 是 Flutter 生态中最权威、最流行的网络状态监听库,由 fluttercommunity.dev 官方维护。它就像一位长着翅膀的"网络小天使",时刻守护着你的应用与网络世界的连接。无论是 Android 的 WiFi、iOS 的移动网络,还是现在鸿蒙的各种网络形态,它都能轻松驾驭!🎯

在鸿蒙开发中,这位"小天使"的价值尤为突出。想象一下,你的应用需要在 WiFi 环境下下载大文件、在移动网络下提醒用户注意流量消耗、在离线时展示缓存数据——这些贴心的功能,都离不开网络状态监听的支撑!

1.2 鸿蒙网络监听的"特殊挑战"

不过,话说回来,鸿蒙系统的网络体系可不是一般的"对手"。它和 Android、iOS 虽然有些相似之处,但在权限模型、网络状态映射、事件响应机制等方面都有着独特的个性。就像三个性格不同的朋友,虽然都很友好,但相处方式可不一样呢!🌸

举个例子,同样是监听网络状态变化,Android 用的是 ConnectivityManager,iOS 用的是 SCNetworkReachability,而鸿蒙呢?人家有自己的 @ohos.net.connection 模块!所以,适配工作绝对不是简单的"拿来主义",需要我们做一些"本土化"的工作~

二、技术方案:三重奏——Service + Provider + Widgets

2.1 优雅架构,层层递进

在鸿蒙平台上使用 connectivity_plus,我们不只是简单地调用 API 就完事了。为了让代码更优雅、更易维护、更易扩展,博主采用了"三重奏"架构:

  • ConnectivityService:负责底层网络状态监听,是整个架构的"基石"
  • ConnectivityProvider:用 Riverpod 进行状态管理,是连接 Service 和 UI 的"桥梁"
  • ConnectivityWidgets:提供可复用的 UI 组件,是展示给用户看的"门面"

这种架构就像一个井然有序的小团队,每个人各司其职,配合得天衣无缝!🎭

2.2 核心功能一览表

让我们来看看这套方案能为你带来哪些强大功能吧:

功能模块 描述 鸿蒙支持度
网络状态监听 WiFi/移动网络/以太网/无连接 ✅ 完美支持
状态变化回调 离线→联网、联网→离线、网络类型切换 ✅ 实时响应
历史记录 记录网络状态变化历史 ✅ 最多保存 50 条
自动刷新 网络恢复后自动触发数据刷新 ✅ 可自定义任务
离线提示 红色横幅提醒用户 ✅ 支持自动消失
恢复通知 绿色横幅告知用户网络已恢复 ✅ 带动画效果

2.3 关键数据模型

在开始写代码之前,先让我们认识一下几个核心的数据模型,它们就像是整个系统的"骨架",支撑着所有功能的运行!

/// 网络连接类型枚举
enum ConnectionType {
  wifi,           /// WiFi 无线网络
  mobile,         /// 移动网络(4G/5G)
  ethernet,       /// 以太网有线网络
  bluetooth,      /// 蓝牙共享网络
  vpn,            /// VPN 虚拟专用网络
  other,          /// 其他网络类型
  none,           /// 无网络连接
}

/// 网络连接状态数据模型
class ConnectivityInfo {
  final List<ConnectionType> connectionTypes;  /// 当前网络连接类型列表
  final bool isConnected;                        /// 是否已连接(任意网络)
  final bool isHighQuality;                      /// 是否为高质量网络(WiFi/以太网)
  final DateTime? lastChangedAt;                /// 上次状态变化时间戳
  final List<ConnectivityHistoryEntry> history; /// 网络变化历史记录
  
  /// 判断是否为特定连接类型
  bool isType(ConnectionType type) => connectionTypes.contains(type);
  
  /// 获取主要的连接类型(用于显示)
  ConnectionType? get primaryType { ... }
}

/// 网络状态变化历史记录条目
class ConnectivityHistoryEntry {
  final DateTime timestamp;                     /// 变化时间戳
  final bool fromConnected;                     /// 变化前的连接状态
  final bool toConnected;                       /// 变化后的连接状态
  final ConnectivityChangeType changeType;     /// 变化类型
  
  /// 是否从离线变为联网
  bool get wasReconnected => !fromConnected && toConnected;
  
  /// 是否从联网变为离线
  bool get wasDisconnected => fromConnected && !toConnected;
}

这些模型设计得是不是很贴心呀?它们不仅封装了原始的网络状态,还提供了很多便捷的派生属性,让我们在业务代码中使用起来超级方便!🎀

三、手把手实战:打造你的网络状态监控系统

3.1 项目配置第一步

首先,我们需要在项目的 pubspec.yaml 中添加依赖。鸿蒙环境下,标准版的 connectivity_plus 已经支持得很好啦:

dependencies:
  flutter:
    sdk: flutter
  
  # 网络状态监听库 - OpenHarmony 兼容
  connectivity_plus: ^6.0.3
  
  # 状态管理 - 用 Riverpod 让状态管理更优雅
  flutter_riverpod: ^2.5.1

添加完依赖后,执行激动人心的命令:

flutter pub get

如果一切顺利,你就会看到控制台输出一堆绿色的成功信息,那种感觉,就像收到了心仪已久的礼物一样开心!🎁

3.2 基石层:ConnectivityService 实现

现在,让我们来构建整个系统的"基石"——ConnectivityService。这个服务负责所有底层的网络状态监听工作,是整个架构中最核心的部分!

import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import '../models/connectivity_info.dart';

/// 网络连接状态服务
class ConnectivityService {
  static ConnectivityService? _instance;
  static ConnectivityService get instance => _instance ??= ConnectivityService._();

  ConnectivityService._();

  final Connectivity _connectivity = Connectivity();
  ConnectivityInfo _currentInfo = ConnectivityInfo.empty();
  final StreamController<ConnectivityInfo> _connectivityController =
      StreamController<ConnectivityInfo>.broadcast();
  final List<ConnectivityHistoryEntry> _history = [];
  bool _isListening = false;
  bool? _lastConnected;

  Stream<ConnectivityInfo> get connectivityStream => _connectivityController.stream;
  ConnectivityInfo get currentInfo => _currentInfo;
  List<ConnectivityHistoryEntry> get history => List.unmodifiable(_history);
  bool get isConnected => _currentInfo.isConnected;
  bool get isListening => _isListening;

  Future<ConnectivityInfo> init() async {
    await _updateConnectivity();
    if (!_isListening) {
      _startListening();
    }
    return _currentInfo;
  }

  void _startListening() {
    if (_isListening) return;

    _connectivity.onConnectivityChanged.listen(
      _handleConnectivityChange,
      onError: (error) {
        debugPrint('[ConnectivityService] 监听错误: $error');
      },
    );

    _isListening = true;
    debugPrint('[ConnectivityService] 网络状态监听已启动');
  }

  void _handleConnectivityChange(List<ConnectivityResult> results) {
    _updateConnectivity(results: results);
    _connectivityController.add(_currentInfo);

    if (_currentInfo.isConnected && _lastConnected == false) {
      debugPrint('[ConnectivityService] 网络已恢复,触发 onNetworkRestored 回调');
      for (final callback in _onNetworkRestoredCallbacks) {
        try {
          callback(_currentInfo);
        } catch (e) {
          debugPrint('[ConnectivityService] onNetworkRestored 回调错误: $e');
        }
      }
    }

    if (!_currentInfo.isConnected && _lastConnected == true) {
      debugPrint('[ConnectivityService] 网络已断开,触发 onNetworkLost 回调');
      for (final callback in _onNetworkLostCallbacks) {
        try {
          callback(_currentInfo);
        } catch (e) {
          debugPrint('[ConnectivityService] onNetworkLost 回调错误: $e');
        }
      }
    }

    _lastConnected = _currentInfo.isConnected;
  }

  Future<void> _updateConnectivity({List<ConnectivityResult>? results}) async {
    try {
      List<ConnectivityResult> connectivityResults;
      if (results != null) {
        connectivityResults = results;
      } else {
        connectivityResults = await _connectivity.checkConnectivity();
      }

      _recordHistory(connectivityResults);
      _currentInfo = ConnectivityInfo.fromConnectivityResults(
        connectivityResults,
        lastChangedAt: DateTime.now(),
        history: List.unmodifiable(_history),
      );

      debugPrint('[ConnectivityService] 网络状态已更新: $_currentInfo');
    } catch (e) {
      debugPrint('[ConnectivityService] 获取网络状态失败: $e');
    }
  }

  void _recordHistory(List<ConnectivityResult> results) {
    final wasConnected = _lastConnected ?? _currentInfo.isConnected;
    final nowConnected = results.isNotEmpty && !results.contains(ConnectivityResult.none);

    if (wasConnected == nowConnected && _history.isNotEmpty) {
      return;
    }

    final fromTypes = _currentInfo.connectionTypes;
    final toTypes = results.map(_mapResultToType).toList();

    ConnectivityChangeType changeType;
    if (!wasConnected && nowConnected) {
      changeType = ConnectivityChangeType.offlineToOnline;
    } else if (wasConnected && !nowConnected) {
      changeType = ConnectivityChangeType.onlineToOffline;
    } else {
      if (fromTypes.contains(ConnectionType.wifi) && toTypes.contains(ConnectionType.mobile)) {
        changeType = ConnectivityChangeType.wifiToMobile;
      } else if (fromTypes.contains(ConnectionType.mobile) && toTypes.contains(ConnectionType.wifi)) {
        changeType = ConnectivityChangeType.mobileToWifi;
      } else {
        changeType = ConnectivityChangeType.typeChanged;
      }
    }

    final entry = ConnectivityHistoryEntry(
      timestamp: DateTime.now(),
      fromConnected: wasConnected,
      toConnected: nowConnected,
      fromTypes: fromTypes,
      toTypes: toTypes,
      changeType: changeType,
    );

    _history.add(entry);
    if (_history.length > 50) {
      _history.removeAt(0);
    }

    debugPrint('[ConnectivityService] 记录历史: ${entry.description}');
  }

  ConnectionType _mapResultToType(ConnectivityResult result) {
    switch (result) {
      case ConnectivityResult.wifi:
        return ConnectionType.wifi;
      case ConnectivityResult.bluetooth:
        return ConnectionType.bluetooth;
      case ConnectivityResult.ethernet:
        return ConnectionType.ethernet;
      case ConnectivityResult.mobile:
        return ConnectionType.mobile;
      case ConnectivityResult.vpn:
        return ConnectionType.vpn;
      case ConnectivityResult.other:
        return ConnectionType.other;
      case ConnectivityResult.none:
        return ConnectionType.none;
      case ConnectivityResult.satellite:
        return ConnectionType.other;
    }
  }

  final List<Function(ConnectivityInfo)> _onNetworkRestoredCallbacks = [];
  final List<Function(ConnectivityInfo)> _onNetworkLostCallbacks = [];

  void onNetworkRestored(Function(ConnectivityInfo) callback) {
    _onNetworkRestoredCallbacks.add(callback);
  }

  void onNetworkLost(Function(ConnectivityInfo) callback) {
    _onNetworkLostCallbacks.add(callback);
  }

  void removeOnNetworkRestored(Function(ConnectivityInfo) callback) {
    _onNetworkRestoredCallbacks.remove(callback);
  }

  void removeOnNetworkLost(Function(ConnectivityInfo) callback) {
    _onNetworkLostCallbacks.remove(callback);
  }

  Future<ConnectivityInfo> checkConnectivity() async {
    await _updateConnectivity();
    return _currentInfo;
  }

  void clearHistory() {
    _history.clear();
  }

  void dispose() {
    stopListening();
    _connectivityController.close();
    _onNetworkRestoredCallbacks.clear();
    _onNetworkLostCallbacks.clear();
    debugPrint('[ConnectivityService] 资源已释放');
  }
}

mixin ConnectivityListenerMixin {
  void onNetworkRestored(ConnectivityInfo info) {}
  void onNetworkLost(ConnectivityInfo info) {}

  ConnectivityService get _connectivityService => ConnectivityService.instance;

  void initConnectivityListener() {
    _connectivityService.onNetworkRestored(onNetworkRestored);
    _connectivityService.onNetworkLost(onNetworkLost);
  }

  void disposeConnectivityListener() {
    _connectivityService.removeOnNetworkRestored(onNetworkRestored);
    _connectivityService.removeOnNetworkLost(onNetworkLost);
  }
}

哇,这个 Service 是不是超级强大?它不仅监听网络状态变化,还记录历史、提供回调机制,甚至还有一个贴心的 Mixin 让页面可以快速集成!💕

3.3 桥梁层:ConnectivityProvider 实现

有了 Service 这个"基石",接下来我们需要一座"桥梁"来连接 Service 和 UI 层——那就是 ConnectivityProvider!用 Riverpod 来做状态管理,真的是再合适不过了~

import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/connectivity_info.dart';
import '../services/connectivity_service.dart';

class ConnectivityState {
  final ConnectivityInfo connectivityInfo;
  final bool isListening;
  final List<ConnectivityHistoryEntry> history;
  final int changeCount;

  const ConnectivityState({
    required this.connectivityInfo,
    this.isListening = false,
    this.history = const [],
    this.changeCount = 0,
  });

  factory ConnectivityState.initial() => const ConnectivityState(
    connectivityInfo: ConnectivityInfo(
      connectionTypes: [],
      isConnected: false,
      isHighQuality: false,
    ),
  );

  bool get isConnected => connectivityInfo.isConnected;
  bool get isOffline => !connectivityInfo.isConnected;
  String get connectionTypeName => connectivityInfo.connectionTypeName;

  ConnectivityState copyWith({
    ConnectivityInfo? connectivityInfo,
    bool? isListening,
    List<ConnectivityHistoryEntry>? history,
    int? changeCount,
  }) {
    return ConnectivityState(
      connectivityInfo: connectivityInfo ?? this.connectivityInfo,
      isListening: isListening ?? this.isListening,
      history: history ?? this.history,
      changeCount: changeCount ?? this.changeCount,
    );
  }
}

class ConnectivityNotifier extends Notifier<ConnectivityState> {
  StreamSubscription<ConnectivityInfo>? _subscription;

  
  ConnectivityState build() {
    _initConnectivity();
    return ConnectivityState.initial();
  }

  Future<void> _initConnectivity() async {
    await ConnectivityService.instance.init();
    final info = ConnectivityService.instance.currentInfo;
    state = state.copyWith(
      connectivityInfo: info,
      isListening: true,
    );

    _subscription?.cancel();
    _subscription = ConnectivityService.instance.connectivityStream.listen(
      _onConnectivityChanged,
    );

    ConnectivityService.instance.onNetworkRestored(_onNetworkRestored);
    ConnectivityService.instance.onNetworkLost(_onNetworkLost);
  }

  void _onConnectivityChanged(ConnectivityInfo info) {
    final history = ConnectivityService.instance.history;
    state = state.copyWith(
      connectivityInfo: info,
      history: history,
      changeCount: state.changeCount + 1,
    );
  }

  void _onNetworkRestored(ConnectivityInfo info) {
    state = state.copyWith(
      connectivityInfo: info,
      history: ConnectivityService.instance.history,
      changeCount: state.changeCount + 1,
    );
  }

  void _onNetworkLost(ConnectivityInfo info) {
    state = state.copyWith(
      connectivityInfo: info,
      history: ConnectivityService.instance.history,
      changeCount: state.changeCount + 1,
    );
  }

  Future<void> checkConnectivity() async {
    final info = await ConnectivityService.instance.checkConnectivity();
    state = state.copyWith(connectivityInfo: info);
  }

  void clearHistory() {
    ConnectivityService.instance.clearHistory();
    state = state.copyWith(history: []);
  }
}

final connectivityProvider = NotifierProvider<ConnectivityNotifier, ConnectivityState>(() {
  return ConnectivityNotifier();
});

final isConnectedProvider = Provider<bool>((ref) {
  return ref.watch(connectivityProvider).isConnected;
});

final isOfflineProvider = Provider<bool>((ref) {
  return ref.watch(connectivityProvider).isOffline;
});

final connectionTypeNameProvider = Provider<String>((ref) {
  return ref.watch(connectivityProvider).connectionTypeName;
});

class NetworkRefreshCallback {
  final String key;
  final Future<void> Function() onRefresh;
  final DateTime? lastRefreshTime;

  const NetworkRefreshCallback({
    required this.key,
    required this.onRefresh,
    this.lastRefreshTime,
  });
}

class NetworkRefreshManagerState {
  final Map<String, NetworkRefreshCallback> callbacks;
  final int refreshCount;
  final DateTime? lastRefreshTime;

  const NetworkRefreshManagerState({
    this.callbacks = const {},
    this.refreshCount = 0,
    this.lastRefreshTime,
  });
}

class NetworkRefreshManagerNotifier extends Notifier<NetworkRefreshManagerState> {
  
  NetworkRefreshManagerState build() {
    ref.listen<ConnectivityState>(connectivityProvider, (previous, next) {
      if (previous != null && !previous.isConnected && next.isConnected) {
        _triggerAutoRefresh();
      }
    });
    return const NetworkRefreshManagerState();
  }

  void register({
    required String key,
    required Future<void> Function() onRefresh,
  }) {
    final callback = NetworkRefreshCallback(
      key: key,
      onRefresh: onRefresh,
      lastRefreshTime: DateTime.now(),
    );
    final newCallbacks = Map<String, NetworkRefreshCallback>.from(state.callbacks);
    newCallbacks[key] = callback;
    state = state.copyWith(callbacks: newCallbacks);
  }

  void unregister(String key) {
    final newCallbacks = Map<String, NetworkRefreshCallback>.from(state.callbacks);
    newCallbacks.remove(key);
    state = state.copyWith(callbacks: newCallbacks);
  }

  Future<void> refreshAll() async {
    await _triggerAutoRefresh();
  }

  Future<void> _triggerAutoRefresh() async {
    if (state.callbacks.isEmpty) return;
    for (final callback in state.callbacks.values) {
      try {
        await callback.onRefresh();
      } catch (e) {
      }
    }
    state = state.copyWith(
      refreshCount: state.refreshCount + 1,
      lastRefreshTime: DateTime.now(),
    );
  }
}

final networkRefreshManagerProvider =
    NotifierProvider<NetworkRefreshManagerNotifier, NetworkRefreshManagerState>(() {
  return NetworkRefreshManagerNotifier();
});

太棒了!这个 Provider 不仅管理着网络状态,还提供了一个 networkRefreshManagerProvider 来管理网络恢复后的自动刷新任务——是不是超级贴心?🎈

3.4 门面层:精美 UI 组件

现在,让我们来打造精美的 UI 组件,让用户能够直观地看到网络状态变化!这些组件就像一个个精美的"小礼物",让用户感受到你的用心~

3.4.1 ConnectivityBanner:离线提示与恢复通知

这是一个超级实用的横幅组件,在离线时显示红色提醒,网络恢复时显示绿色通知,还带动画效果呢!

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/connectivity_info.dart';
import '../providers/connectivity_provider.dart';

class ConnectivityBanner extends ConsumerStatefulWidget {
  final Widget child;
  final bool showOfflineBanner;
  final bool showRestoredNotification;
  final int? offlineBannerDuration;
  final int restoredNotificationDuration;

  const ConnectivityBanner({
    super.key,
    required this.child,
    this.showOfflineBanner = true,
    this.showRestoredNotification = true,
    this.offlineBannerDuration,
    this.restoredNotificationDuration = 3,
  });

  
  ConsumerState<ConnectivityBanner> createState() => _ConnectivityBannerState();
}

class _ConnectivityBannerState extends ConsumerState<ConnectivityBanner> {
  bool _showRestored = false;
  bool _wasOffline = false;

  
  Widget build(BuildContext context) {
    final connectivityState = ref.watch(connectivityProvider);
    final isConnected = connectivityState.isConnected;

    if (!_wasOffline && !isConnected) {
      _wasOffline = true;
    } else if (_wasOffline && isConnected && widget.showRestoredNotification) {
      _showRestored = true;
      _wasOffline = false;
      Future.delayed(Duration(seconds: widget.restoredNotificationDuration), () {
        if (mounted) {
          setState(() {
            _showRestored = false;
          });
        }
      });
    }

    return Column(
      children: [
        if (_showRestored && widget.showRestoredNotification)
          _buildRestoredBanner(context),
        if (!isConnected && widget.showOfflineBanner)
          _buildOfflineBanner(context, connectivityState),
        Expanded(child: widget.child),
      ],
    );
  }

  Widget _buildOfflineBanner(BuildContext context, ConnectivityState state) {
    return AnimatedContainer(
      duration: const Duration(milliseconds: 300),
      width: double.infinity,
      padding: EdgeInsets.only(
        top: MediaQuery.of(context).padding.top + 8,
        left: 16,
        right: 16,
        bottom: 12,
      ),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [
            Colors.red[400]!,
            Colors.red[600]!,
          ],
        ),
        boxShadow: [
          BoxShadow(
            color: Colors.red.withValues(alpha: 0.3),
            blurRadius: 8,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: SafeArea(
        bottom: false,
        child: Row(
          children: [
            const Icon(
              Icons.signal_wifi_off,
              color: Colors.white,
              size: 24,
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Text(
                    '网络连接已断开',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 2),
                  Text(
                    _getOfflineMessage(state),
                    style: TextStyle(
                      color: Colors.white.withValues(alpha: 0.9),
                      fontSize: 13,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildRestoredBanner(BuildContext context) {
    return AnimatedContainer(
      duration: const Duration(milliseconds: 300),
      width: double.infinity,
      padding: EdgeInsets.only(
        top: MediaQuery.of(context).padding.top + 8,
        left: 16,
        right: 16,
        bottom: 12,
      ),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [
            Colors.green[400]!,
            Colors.green[600]!,
          ],
        ),
        boxShadow: [
          BoxShadow(
            color: Colors.green.withValues(alpha: 0.3),
            blurRadius: 8,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: SafeArea(
        bottom: false,
        child: Row(
          children: [
            const Icon(
              Icons.signal_wifi_4_bar,
              color: Colors.white,
              size: 24,
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Text(
                    '网络已恢复',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 2),
                  Text(
                    '正在刷新数据...',
                    style: TextStyle(
                      color: Colors.white.withValues(alpha: 0.9),
                      fontSize: 13,
                    ),
                  ),
                ],
              ),
            ),
            const SizedBox(
              width: 20,
              height: 20,
              child: CircularProgressIndicator(
                strokeWidth: 2,
                valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
              ),
            ),
          ],
        ),
      ),
    );
  }

  String _getOfflineMessage(ConnectivityState state) {
    if (state.connectivityInfo.offlineDuration != null) {
      final duration = state.connectivityInfo.offlineDuration!;
      if (duration.inMinutes > 0) {
        return '已离线 ${duration.inMinutes} 分钟';
      } else {
        return '已离线 ${duration.inSeconds} 秒';
      }
    }
    return '请检查您的网络连接';
  }
}

这个横幅组件是不是超级精美?红色的离线提醒、绿色的恢复通知,还有渐变背景和阴影效果,简直是"颜值与实力并存"!🌟

3.4.2 ConnectivityIndicator:小巧的网络状态指示器

如果你只需要一个小巧的指示器放在 AppBar 里,那 ConnectivityIndicator 就再合适不过了~

class ConnectivityIndicator extends ConsumerWidget {
  final double size;
  final bool showLabel;
  final VoidCallback? onTap;

  const ConnectivityIndicator({
    super.key,
    this.size = 20,
    this.showLabel = false,
    this.onTap,
  });

  
  Widget build(BuildContext context, WidgetRef ref) {
    final connectivityState = ref.watch(connectivityProvider);
    final isConnected = connectivityState.isConnected;
    final info = connectivityState.connectivityInfo;

    IconData icon;
    Color color;

    if (isConnected) {
      switch (info.primaryType) {
        case ConnectionType.wifi:
          icon = Icons.wifi;
          color = Colors.green;
          break;
        case ConnectionType.mobile:
          icon = Icons.signal_cellular_alt;
          color = Colors.blue;
          break;
        case ConnectionType.ethernet:
          icon = Icons.settings_ethernet;
          color = Colors.teal;
          break;
        default:
          icon = Icons.signal_wifi_4_bar;
          color = Colors.grey;
      }
    } else {
      icon = Icons.signal_wifi_off;
      color = Colors.red;
    }

    final indicator = Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(icon, size: size, color: color),
        if (showLabel) ...[
          const SizedBox(width: 4),
          Text(
            info.connectionTypeName,
            style: TextStyle(
              fontSize: size * 0.7,
              color: color,
            ),
          ),
        ],
      ],
    );

    if (onTap != null) {
      return GestureDetector(
        onTap: onTap,
        child: indicator,
      );
    }

    return indicator;
  }
}

小巧精致,放在 AppBar 里刚刚好,用户一眼就能看到当前的网络状态!🌸

3.5 集成到应用中

现在,让我们把这些组件集成到应用中,看看效果如何!

首先,在你的 main.dart 中用 ProviderScope 包裹整个应用:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'widgets/connectivity_banner.dart';

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter OH Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ConnectivityBanner(
        child: HomePage(),
      ),
    );
  }
}

然后,在需要网络恢复自动刷新的页面中,注册刷新任务:

class MyDataPage extends ConsumerStatefulWidget {
  const MyDataPage({super.key});

  
  ConsumerState<MyDataPage> createState() => _MyDataPageState();
}

class _MyDataPageState extends ConsumerState<MyDataPage> {
  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      ref.read(networkRefreshManagerProvider.notifier).register(
        key: 'myData',
        onRefresh: () async {
          await ref.read(myDataProvider.notifier).loadData();
        },
      );
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('数据页面'),
        actions: [
          ConnectivityIndicator(showLabel: true),
          const SizedBox(width: 16),
        ],
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    final isOffline = ref.watch(isOfflineProvider);
    
    if (isOffline) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.wifi_off, size: 80, color: Colors.grey[400]),
            const SizedBox(height: 24),
            Text(
              '网络连接不可用',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
                color: Colors.grey[700],
              ),
            ),
          ],
        ),
      );
    }
    
    return _buildDataList();
  }
}

哇,这样就完成了!整个应用现在拥有了完整的网络状态监听、离线提示和自动刷新功能!🎉

四、避坑指南:那些年我们踩过的网络监听"地雷"

4.1 权限配置的"小秘密"

在鸿蒙平台上使用网络功能,权限配置可是非常重要的哦!虽然 connectivity_plus 本身不需要特别的权限,但如果你的应用需要访问网络,记得在 module.json5 中声明:

"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET"
  },
  {
    "name": "ohos.permission.GET_NETWORK_INFO"
  }
]

这两个权限是网络相关功能的基础,可别忘了加上哦~

4.2 模拟器的"虚假网络"

如果你在鸿蒙模拟器上测试,可能会发现网络状态永远显示"已连接"——这不是你的代码有问题,而是模拟器本身就一直模拟网络连接状态!

建议:最终的功能验证一定要在真实设备上进行!毕竟,真实的网络环境才能考验出真正的实力!💪

4.3 状态监听的"生命周期"

在使用 ConnectivityService 的回调时,一定要注意生命周期管理!如果在页面销毁时没有移除回调,可能会导致内存泄漏哦!

别怕,我们已经贴心地提供了 ConnectivityListenerMixin,只要在页面中这样使用就好:

class _MyPageState extends State<MyPage> with ConnectivityListenerMixin {
  
  void initState() {
    super.initState();
    initConnectivityListener();
  }

  
  void dispose() {
    disposeConnectivityListener();
    super.dispose();
  }

  
  void onNetworkRestored(ConnectivityInfo info) {
    debugPrint('网络恢复啦!');
  }
}

这样就安全啦!🔒

五、完整示例:网络状态监控中心

最后,博主给大家准备了一个"大礼包"——一个完整的网络状态监控中心界面。这个界面可以展示网络状态详情、历史记录、自动刷新设置等,功能超级丰富!
这是我的运行截图:在这里插入图片描述

六、总结与展望

好啦,今天的分享就到这里!通过本文,我们学会了:

  1. 如何配置 connectivity_plus 依赖,让它在鸿蒙平台上顺利运行
  2. 如何设计优雅的三层架构(Service + Provider + Widgets),让代码更易维护
  3. 如何实现网络状态实时监听,包括 WiFi/移动网络/无连接的状态变化
  4. 如何打造精美的离线提示和恢复通知,提升用户体验
  5. 如何实现网络恢复后的自动数据刷新,让用户感受到你的贴心
  6. 如何规避权限配置、模拟器测试、生命周期管理等常见的"坑"

网络状态监听是提升应用用户体验的重要一环,但绝不是最后一环。希望大家能够举一反三,将这些知识应用到实际项目中,打造出更加懂用户、懂网络的优质应用!

最后,如果你在实践过程中遇到任何问题,欢迎在评论区留言交流。也欢迎大家关注博主的其他文章,我们会持续带来更多 Flutter 鸿蒙化的实战技术分享!

我们下期再见!拜拜~ 👋


本文同步发布于开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐