Интернет подключен... Но ничего не загружается?" Как я решил эту проблему в Flutter с помощью BLoC (BLoC Часть-4)
FlutterPulseЭта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Давайте посмотрим, как я это сделал — шаг за шагом.
Шаг 1: Требуемые пакеты
Чтобы все это работало, вам понадобится несколько пакетов Flutter.
Вот как добавить их:
- Откройте свой проект Flutter.
- Откройте файл под названием
pubspec.yamlв корне вашего проекта. - Ниже
dependencies:добавьте следующие строки:
dependencies: cupertino_icons: ^1.0.8 flutter_bloc: ^9.1.1 equatable: ^2.0.7 connectivity_plus: ^6.1.4 http: ^1.4.0
Теперь сохраните файл и выполните команду в терминале
flutter pub get
Это скачает и установит все пакеты.
Шаг 2: Создайте internet_event.dart (BLoC Events)
События — это как триггеры в шаблоне BLoC. Они представляют события в вашем приложении или в реальном мире, которые должны вызвать изменение.
В этом проекте есть три возможных события:
I. InternetConnectedEvent
Это событие срабатывает, когда:
- Ваше устройство подключено к Wi-Fi или мобильным данным
- И есть успешный ответ от Google (что означает, что интернет действительно работает)
class InternetConnectedEvent extends InternetEvent {
final ConnectivityResult connectionType;
const InternetConnectedEvent({required this.connectionType});
@override
List<Object> get props => [connectionType];
}
II. InternetDisconnectedEvent
Это событие срабатывает, когда:
- Ваше устройство не имеет сетевого подключения.
- Ни Wi-Fi, ни мобильные данные недоступны.
class InternetDisconnectedEvent extends InternetEvent {
const InternetDisconnectedEvent();
}
III. InternetNoSpeedEvent
Это событие запускается при:
- Устройство технически соединено с Wi-Fi или мобильными данными.
- НО ваше приложение не может добраться до Google (или любого другого сайта, который вы проверяете).
Это может произойти, если:
Маршрутизатор Wi-Fi подключен, но не имеет интернета от провайдера.
Мобильные данные не имеют сигнала, несмотря на то, что они включены.
class InternetNoSpeedEvent extends InternetEvent {
const InternetNoSpeedEvent();
}
Полный код события находится ниже.
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:equatable/equatable.dart';
abstract class InternetEvent extends Equatable {
const InternetEvent();
@override
List<Object> get props => [];
}
class InternetConnectedEvent extends InternetEvent {
final ConnectivityResult connectionType;
const InternetConnectedEvent({required this.connectionType});
@override
List<Object> get props => [connectionType];
}
class InternetDisconnectedEvent extends InternetEvent {
const InternetDisconnectedEvent();
}
class InternetNoSpeedEvent extends InternetEvent {
const InternetNoSpeedEvent();
}
Шаг 3: internet_state.dart (Состояния BLoC)
Состояния в BLoC представляют то, что происходит прямо сейчас в вашем приложении. Подумайте о состояниях как о текущем статусе вашего интернет-соединения.
В этом проекте есть четыре возможных состояния:
I. InternetLoading
Это начальное состояние.
Когда это происходит?
- Когда приложение запускается впервые.
- До того, как мы что-либо узнаем о соединении.
class InternetLoading extends InternetState {}
II. InternetConnected
Это состояние означает:
- Ваше устройство соединено с Wi-Fi или мобильными данными.
- И есть действительное интернет-соединение (например, Google отвечает).
Когда это происходит?
- Сразу после успешного HTTP-запроса к Google.
- Или через Wi-Fi, или через мобильные данные.
class InternetConnected extends InternetState {
final ConnectivityResult connectionType;
const InternetConnected({required this.connectionType});
@override
List<Object> get props => [connectionType];
}
III. InternetDisconnected
Этот статус означает:
- Ваше устройство совершенно не подключено к сети.
- Не подключено к Wi-Fi или мобильным данным.
Когда это происходит?
- Когда соединение полностью потеряно.
- Нет сигнала, режим самолета или Wi-Fi выключен.
class InternetDisconnected extends InternetState {
final String message;
const InternetDisconnected({required this.message});
@override
List<Object> get props => [message];
}
IV. InternetNoSpeed
Это самый интересный статус!
Он означает:
- Технически вы подключены (Wi-Fi или мобильные данные).
- Но интернет недоступен или слишком медленный, чтобы ответить.
Когда это происходит?
- Когда маршрутизатор включен, но нет интернета от провайдера.
- Когда мобильные данные показывают сигнал, но вы не можете добраться до сайтов.
- Когда сеть блокирует трафик.
class InternetNoSpeed extends InternetState {
final String message;
const InternetNoSpeed({required this.message});
@override
List<Object> get props => [message];
}
Полный код состояния ниже.
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:equatable/equatable.dart';
abstract class InternetState extends Equatable {
const InternetState();
@override
List<Object> get props => [];
}
class InternetLoading extends InternetState {}
class InternetConnected extends InternetState {
final ConnectivityResult connectionType;
const InternetConnected({required this.connectionType});
@override
List<Object> get props => [connectionType];
}
class InternetDisconnected extends InternetState {
final String message;
const InternetDisconnected({required this.message});
@override
List<Object> get props => [message];
}
class InternetNoSpeed extends InternetState {
final String message;
const InternetNoSpeed({required this.message});
@override
List<Object> get props => [message];
}
Шаг 4: Создайте internet_bloc.dart (логика BLoC)
В internet_bloc.dart мы определяем BLoC, который подписывается на изменения соединения с помощью connectivity_plus и устанавливает периодический таймер для проверки доступности интернета каждые несколько секунд. Он отправляет HTTP-запрос HEAD в Google для проверки реального доступа к интернету. На основе ответа или статуса соединения он отправляет одно из трех событий - соединение, разрыв соединения или отсутствие скорости - которые BLoC обрабатывает для излучения соответствующих состояний. Это гарантирует, что интерфейс пользователя отражает истинные условия интернета, а не просто типы сетевого подключения.
import 'dart:async';
import 'dart:developer';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:http/http.dart' as http;
import 'internet_event.dart';
import 'internet_state.dart';
class InternetBloc extends Bloc<InternetEvent, InternetState> {
final Connectivity _connectivity;
late final StreamSubscription<List<ConnectivityResult>> _connectivityStreamSubscription;
late final Timer _internetCheckTimer;
InternetBloc({required Connectivity connectivity})
: _connectivity = connectivity,
super(InternetLoading()) {
on<InternetConnectedEvent>((event, emit) {
emit(InternetConnected(connectionType: event.connectionType));
});
on<InternetDisconnectedEvent>((event, emit) {
emit(const InternetDisconnected(message: 'Нет интернет-соединения'));
});
on<InternetNoSpeedEvent>((event, emit) {
emit(const InternetNoSpeed(message: 'Подключено, но нет интернет-соединения'));
});
_connectivityStreamSubscription = _connectivity.onConnectivityChanged.listen((connectivityResultList) {
_checkInternetSpeed(connectivityResultList.first);
});
_internetCheckTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
final result = await _connectivity.checkConnectivity();
_checkInternetSpeed(result.first);
});
}
Future<void> _checkInternetSpeed(ConnectivityResult result) async {
if (result == ConnectivityResult.mobile || result == ConnectivityResult.wifi) {
try {
final response = await http.head(Uri.parse('https://www.google.com'));
if (response.statusCode == 200) {
add(InternetConnectedEvent(connectionType: result));
} else {
add(const InternetNoSpeedEvent());
}
} catch (e) {
add(const InternetNoSpeedEvent());
}
} else {
add(const InternetDisconnectedEvent());
}
}
@override
Future<void> close() {
_connectivityStreamSubscription.cancel();
_internetCheckTimer.cancel();
return super.close();
}
}
Шаг 5: Настройка main.dart (Точка входа приложения)
В main.dart мы настраиваем приложение Flutter, инициализируя BLoC для проверки интернет-соединения и оборачивая все приложение в BlocProvider, чтобы все виджеты могли получить доступ к состоянию интернет-соединения. Мы определяем тему приложения и запускаем виджет MyHomePage в качестве домашнего экрана, который будет визуально реагировать на изменения в интернет-соединении.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'internet_bloc.dart';
import 'home_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => InternetBloc(connectivity: Connectivity()),
child: MaterialApp(
title: 'Демо-приложение Flutter',
theme: ThemeData(primarySwatch: Colors.blue),
home: const MyHomePage(),
),
);
}
}
Шаг 6: home_screen.dart (Инт Amir zemí + Состояние)
В home_screen.dart мы создаем основной интерфейс пользователя с AppBar, цвет которого изменяется в зависимости от текущего состояния интернет-соединения (подключено, не подключено или нет скорости). На экране отображаются кнопки для навигации на три других экрана, что упрощает пользователям возможность изучить приложение, сохраняя при этом реальное состояние интернет-соединения визуально.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'internet_bloc.dart';
import 'screen_one.dart';
import 'screen_two.dart';
import 'screen_three.dart';
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
final internetState = context.watch<InternetBloc>().state;
return Scaffold(
appBar: AppBar(
title: const Text('Домашний экран'),
backgroundColor: internetState is InternetConnected
? Colors.blue
: internetState is InternetNoSpeed
? Colors.orange
: Colors.red,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () {},
child: const Text('Экран Один'),
),
ElevatedButton(
onPressed: () {},
child: const Text('Экран Два'),
),
ElevatedButton(
onPressed: () {},
child: const Text('Экран Три'),
),
],
),
),
);
}
}
🎨 Результат? Умное приложение
Теперь мое приложение делает то, чего нет у большинства приложений не:
✅ Отображает синюю AppBar, когда интернет работает
⚠️ Отображает оранжевый, когда подключено, но не загружается (например, маршрутизатор выключен)
❌ Отображает красный, когда автономно
Это одна из тех "небольших, но мощных" функций, которая кардинально улучшает опыт пользователя.
Если вы когда-либо имели дело с пользователями, которые говорят:
„Но мой телефон ПОДКЛЮЧЕН к Wi-Fi!“
...эта настройка сэкономит вам время и нервы 😅
Продолжайте экспериментировать, оставайтесь любопытными и счастливого программирования! 💙🚀