
Использование Cubit во флаттер на примере отслеживания состояния соединения с интернет.
Приложения со временем растут и постоянно меняются. Для удобства поддержки и масштабирования приложения есть различные подходы. Рассмотрим архитектуру BLoC на примере популярной библиотеки bloc.
Данная библиотека дает нам два основных инструмента: Cubit и BLoC.
Flutter Cubit.
Cubit стримит изменение состояний (State) в UI. Для передачи нового состояния нужно вызвать emit у Cubit. Обычно изменение состояния вызывается со слоя данных, через другой Stream. Но вы можете объявить и функции, вызов которых будет влиять на состояния приложения, например запрашивать какие либо данные. Хотя для этих целей рекомендуется использовать Bloc.
Cubit и Bloc являются слоем бизнес-логики.
Рассмотрим пример отслеживания состояния соединения с интернет:
class ConnectionState { final bool connected; ConnectionState(this.connected): assert(connected != null); } class ConnectionCubit extends Cubit<ConnectionState> { StreamSubscription<ConnectivityResult> _subscription; ConnectionCubit() : super(ConnectionState(false)) { _subscription = Connectivity().onConnectivityChanged .listen((ConnectivityResult result) { emit(ConnectionState(result != ConnectivityResult.none))); }); } @override Future<void> close() { _subscription.cancel(); return super.close(); } }
Нас интересует только наличие подключения или его отсутствие, и не важно Wi-Fi это или нет.
В конструкторе Cubit подпишемся на изменение состояния. Интернет есть при состоянии отличном от ConnectivityResult.none. Когда Cubit будет закрыт, нужно отменить подписку.
Чтобы посмотреть в работе, воспользуемся еще одной библиотекой flutter_bloc. Эта библиотека предоставляет виджеты для работы с Cubit/Bloc в UI.
Виджеты, принимающие параметр Cubit принимают так же и Bloc, так как Bloc наследует Cubit.
Виджет BlocBuilder вызывает функцию builder(BuildContext, State) каждый раз, когда State (состояние) изменяется. Вы можете указать когда именно нужно вызывать данную функцию для перерисовки интерфейса с помощью buildWhen: (previousState, state) или переопределив метод сравнения у класса State.
Созданный нами Cubit можно использовать например так:
void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter App', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: HomePage() ); } } class HomePage extends StatefulWidget { @override State<StatefulWidget> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { ConnectionCubit _cubit; @override void initState() { super.initState(); _cubit = ConnectionCubit(); } @override void dispose() { _cubit?.close(); super.dispose(); } @override Widget build(BuildContext context) => Scaffold( appBar: AppBar( title: Text("Приложение"), ), body: Center( child: BlocBuilder<ConnectionCubit, ConnectionState>( cubit: _cubit, builder: (context, state) => state.connected ? Text('Соединение есть') : Text("Нет соединения с интернет!", style: TextStyle(color: Colors.red),) ), ), ); }
Отключив передачу данных и Wi-Fi мы увидем красную надпись, о том что нет интернета.
Вы могли заметить, что на мгновение, при отключении Wi-Fi, появилась надпись о отсутствии интернета, это связанно с тем, что на телефоне может отключаться мобильная передача данных пока подключена сеть WI-Fi. В. результате на промежуток времени, требуемый для подключения мобильных данных возвращается стейт ConnectivityResult.none.
Можно доработать Cubit, добавив небольшую задержку на изменения стейта на отключения интернета.
class ConnectionCubit extends Cubit<ConnectionState> { StreamSubscription<ConnectivityResult> _subscription; Timer _timer; ConnectionCubit() : super(ConnectionState(false)) { _subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) { _subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) { if (_timer != null && _timer.isActive) { _timer.cancel(); } if (result != ConnectivityResult.none) { emit(ConnectionState(true)); } else { _timer = Timer(Duration(seconds: 1), () => emit(ConnectionState(false))); } }); } @override Future<void> close() { _subscription.cancel(); return super.close(); } }
При изменении состояния на none, запустим таймер на 1 секунду. Если за секунду состояние изменится, то таймер будет остановлен. Если же состояние не изменится, то будет вызван emit.
При появлении интернета вызываем emit незамедлительно.
В библиотеку flutter_bloc есть еще один полезный виджет, это BlocProvider. Данный виджет имеет метод create, который будет вызван при первом обращении к провайдеру в поисках данного Bloc/Cubit. Это поведение унаследовано от библиотеки Provider и его можно изменить при необходимости указав lazy: false. Тогда Bloc/Cubit будет создан сразу.
С помощью BlocProvider можно удобно использовать наш Cubit в StatelessWidget. Также можно разместить провайдер выше навигатора и получить доступ во всех экранах.
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider<ConnectionCubit>( create: (context) => ConnectionCubit(), child: MaterialApp( title: 'Flutter App', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: HomePage() ), ); } } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) => Scaffold( appBar: AppBar( title: Text("Приложение"), ), body: Center( child: BlocBuilder<ConnectionCubit, ConnectionState>( builder: (context, state) => state.connected ? Text('Соединение есть') : Text("Нет соединения с интернет!", style: TextStyle(color: Colors.red),) ), ), ); }
Читайте о Bloc в статье https://terraideas.ru/article/bloc-vo-flutter-otlichie-ot-cubit-i-primer-ispolzovaniya-36.
Комментарии к статье: Cubit во Flutter на примере Connectivity.