Flutter: архитектура BLOC
Основная идея данной архитектуры — отделить отрисовку от логики. Побочная возможность — еще один способ изменения данных виджета из другого виджета, без использования StreamController (ну на самом деле он таки используется но «внутри») и передергивания SetState
Итак, для использования нужно в pubspec.yaml добавить:
1 2 3 |
dependencies: bloc: ^8.1.1 flutter_bloc: ^8.1.2 |
Поставим себе простейшую задачу: нарисовать квадрат и изменять его цвет в зависимости от нажатой кнопки. Для этого оформим отдельный файл, где пропишем два эвента: сменить цвет на красный и зелёный:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; abstract class ColorEvent {} class event_red extends ColorEvent {} class event_green extends ColorEvent {} class ColorBloc extends Bloc<ColorEvent, Color> { ColorBloc() : super(Colors.black) { on<event_red>((event, emit) { emit(Colors.red); }); on<event_green>((event, emit) { emit(Colors.green); } ); } @override void onEvent(ColorEvent event) { super.onEvent(event); print(event); } } |
Далее оформим две страницы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
import 'bloc/color_bloc.dart'; import 'Page2.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider<ColorBloc>( create: (context) => ColorBloc(), child:MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Bloc', initialRoute: '/', routes: { "/": (BuildContext context) =>MyHomePage(), '/Page2':(BuildContext context) => Page2(), }, ) ); } } class MyHomePage extends StatefulWidget { _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { ColorBloc _bloc = BlocProvider.of<ColorBloc>(context); return Scaffold( appBar: AppBar( title: Text('Flutter Bloc'), centerTitle: true, ), body: Center( child: BlocBuilder<ColorBloc, Color>( builder: (context, currentColor) => Container(height: 100, width: 100, color: currentColor, ), ), ), floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ FloatingActionButton( heroTag: "btn3", backgroundColor: Colors.red, onPressed: () {_bloc.add(event_red());}, ), TextButton.icon( onPressed: () { Navigator.pushNamed(context, '/Page2'); }, icon: Icon(Icons.local_bar_sharp), label: Text("Page2") ), ], ), ); } } |
Вторая страница:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'bloc/color_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class Page2 extends StatefulWidget { _Page2State createState() => _Page2State(); } class _Page2State extends State<Page2> { final Page2_scaffoldKey = GlobalKey<ScaffoldState>(); @override Widget build(BuildContext context) { var myBloc = BlocProvider.of<ColorBloc>(context); return Scaffold( key: Page2_scaffoldKey, appBar: AppBar(title: Text('Page2'),), body: FloatingActionButton( heroTag: "btn12", backgroundColor: Colors.green, onPressed: () { myBloc.add(event_green()); }, ), ); } } |
А теперь чуть усложним задачу. Добавим квадрат который будет скрываться при нажатии конпки на втором экране, а еще, для разделения функционала, оформим в папке bloc/notify классы согласно рекомендациям архитектуры. Добавим еще один блок, и разделим его на три файла: bloc, event и state:
event:
1 2 3 4 5 |
abstract class NotifyEvent {} class InitEvent extends NotifyEvent {} class EventShow extends NotifyEvent {} class EventHide extends NotifyEvent {} |
state:
1 2 3 4 5 6 7 8 9 10 11 |
class NotifyState { bool visible=false; NotifyState init(bool visible) { this.visible=visible; return NotifyState(); } NotifyState clone() { return NotifyState(); } } |
bloc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import 'package:bloc/bloc.dart'; import 'event.dart'; import 'state.dart'; class NotifyBloc extends Bloc<NotifyEvent, NotifyState> { NotifyBloc() : super(NotifyState().init(true)) { on<InitEvent>(_init); on<EventShow>((event, emit) { NotifyState visible=NotifyState(); visible.visible=true; emit(visible); }); on<EventHide>((event, emit) { NotifyState visible=NotifyState(); visible.visible=false; emit(visible); }); } void _init(InitEvent event, Emitter<NotifyState> emit) async { emit(state.clone()); } } |
В основном классе виджета будем использовать вместо BlocProvider, MultiBlocProvider:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider<ColorBloc>( create: (context) => ColorBloc(), ), BlocProvider<NotifyBloc>( create: (BuildContext context) => NotifyBloc(), ), ], child: MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Bloc', initialRoute: '/', routes: { "/": (BuildContext context) =>MyHomePage(), '/Page2':(BuildContext context) => Page2(), }, ) ); } } |
Отрисовка квадрата:
1 2 3 4 5 6 7 |
BlocBuilder<NotifyBloc, NotifyState>( builder: (context, currentNotifyState) => Visibility( visible: currentNotifyState.visible, child: Container(height: 100, width: 100, color: Colors.lightBlue,), ) ), |
Кнопка скрытия квадрата на втором экране:
1 2 3 4 5 6 7 8 |
FloatingActionButton( heroTag: "btn12", backgroundColor: Colors.green, onPressed: () { myBloc.add(event_green()); BlocProvider.of<NotifyBloc>(context).add(EventHide()); }, ), |