Интернет подключен... Но ничего не загружается?" Как я решил эту проблему в Flutter с помощью BLoC (BLoC Часть-4)

Интернет подключен... Но ничего не загружается?" Как я решил эту проблему в Flutter с помощью BLoC (BLoC Часть-4)

FlutterPulse

Эта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Давайте посмотрим, как я это сделал — шаг за шагом.

Шаг 1: Требуемые пакеты

Чтобы все это работало, вам понадобится несколько пакетов Flutter.

Вот как добавить их:

  1. Откройте свой проект Flutter.
  2. Откройте файл под названием pubspec.yaml в корне вашего проекта.
  3. Ниже 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!“

...эта настройка сэкономит вам время и нервы 😅

Продолжайте экспериментировать, оставайтесь любопытными и счастливого программирования! 💙🚀

Report Page