#flutter #dart #bloc
#flutter #dart #блок
Вопрос:
В моем проекте я использую архитектуру с пользовательским провайдером.
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider(
{@required this.child,
@required this.bloc,
@required this.blocContext,
Key key})
: super(key: key);
final T bloc;
final Widget child;
final BlocContextBase<T> blocContext;
@override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context) {
final BlocProvider<T> provider = context.findAncestorWidgetOfExactType();
return provider.bloc;
}
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>> {
@override
void initState() {
super.initState();
widget.blocContext.subscribe(widget.bloc, context);
}
@override
void dispose() {
widget.bloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
И все работает хорошо, пока я не переключился на BottomNavigationBar.
class TabContainerContent {
final Widget firstTab;
final Widget secondTab;
final Widget thirdTab;
TabContainerContent(
{@required this.firstTab,
@required this.secondTab,
@required this.thirdTab});
}
class TabContainerScreen extends StatefulWidget {
final TabContainerContent _content;
TabContainerScreen({@required TabContainerContent content})
: _content = content;
@override
_TabContainerScreenState createState() => _TabContainerScreenState();
}
class _TabContainerScreenState extends State<TabContainerScreen> {
int _selectedIndex = 0;
List<BottomNavigationBarItem> _bottomBarItems = [];
List<Widget> _tabWidgets = [];
@override
void initState() {
super.initState();
_tabWidgets.add(widget._content.firstTab);
_bottomBarItems.add(
BottomNavigationBarItem(
icon: Icon(Icons.map),
label: 'Profile',
),
);
_tabWidgets.add(widget._content.secondTab);
_bottomBarItems.add(
BottomNavigationBarItem(
icon: Icon(Icons.location_city),
label: 'Search',
),
);
_tabWidgets.add(widget._content.thirdTab);
_bottomBarItems.add(
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
label: 'Ads',
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _tabWidgets[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
items: _bottomBarItems,
currentIndex: _selectedIndex,
onTap: (int value) {
setState(() {
_selectedIndex = value;
});
},
),
);
}
}
Проблема возникает только в том случае, если в моем блоке есть контроллер потока, и я использую StreamBuilder в своем виджете.
Если я перейду на другую вкладку, а затем вернусь назад. Затем я получаю сообщение о том, что поток ошибок уже прослушан.
Мне кажется, что я где-то не удаляю некоторые данные.
Вот как выглядит blocs .
class ProfileBloc extends IProfileBloc {
final StreamController<User> _userControllerOne = StreamController<User>();
Sink<User> get _inUserState => _userControllerOne.sink;
Stream<User> get outUserState => _userControllerOne.stream;
@override
void dispose() {
_userControllerOne.close();
super.dispose();
}
}
—
class SearchBloc extends ISearchBloc {
final StreamController<User> _userControllerTwo = StreamController<User>();
Sink<User> get _inUserState => _userControllerTwo.sink;
Stream<User> get outUserState => _userControllerTwo.stream;
@override
void dispose() {
_userControllerTwo.close();
super.dispose();
}
}
—
class AdsBloc extends IAdsBloc {
final StreamController<User> _userControllerThree = StreamController<User>();
Sink<User> get _inUserState => _userControllerThree.sink;
Stream<User> get outUserState => _userControllerThree.stream;
@override
void dispose() {
_userControllerThree.close();
super.dispose();
}
}
This is how my UI looks like
class ProfileScreen extends StatefulWidget {
ProfileScreen({Key key}) : super(key: key);
@override
_ProfileScreenState createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
IProfileBloc _bloc;
@override
void initState() {
super.initState();
_bloc = BlocProvider.of(context);
_bloc.loadUser();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: StreamBuilder(
stream: _bloc.outUserState,
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
return Container();
},
),
));
}
}
—
class SearchScreen extends StatefulWidget {
SearchScreen({Key key}) : super(key: key);
@override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
ISearchBloc _bloc;
@override
void initState() {
super.initState();
_bloc = BlocProvider.of(context);
_bloc.loadUser();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: StreamBuilder(
stream: _bloc.outUserState,
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
return Container();
},
),
));
}
}
—
class AdsScreen extends StatefulWidget {
AdsScreen({Key key}) : super(key: key);
@override
_AdsScreenState createState() => _AdsScreenState();
}
class _AdsScreenState extends State<AdsScreen> {
IAdsBloc _bloc;
@override
void initState() {
super.initState();
_bloc = BlocProvider.of(context);
_bloc.loadUser();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: StreamBuilder(
stream: _bloc.outUserState,
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
return Container();
},
),
));
}
}
Я также использую инжектор.
class ApplicationAssembly {
static final Injector injector = Injector('ApplicationAssemblyInjector');
//----------------------------------------------------------------------------
static void initialize() {
_registerManagers();
_registerBlocs();
_registerModuleBuilders();
}
static void _registerManagers() {
}
static void _registerBlocs() {
injector.map<IProfileBloc>((i) => ProfileBloc());
injector.map<IAdsBloc>((i) => AdsBloc());
injector.map<ISearchBloc>((i) => SearchBloc());
}
static void _registerModuleBuilders() {
//TAB BAR MODULES---------------------------------------------------------------
injector.map<ProfileModuleBuilder>((i) => () {
return BlocProvider(
child: ProfileScreen(),
bloc: i.get<IProfileBloc>(),
blocContext: ProfileBlocContext());
});
injector.map<SearchModuleBuilder>((i) => () {
return BlocProvider(
child: SearchScreen(),
bloc: i.get<ISearchBloc>(),
blocContext: SearchBlocContext());
});
injector.map<AdsModuleBuilder>((i) => () {
return BlocProvider(
child: AdsScreen(),
bloc: i.get<IAdsBloc>(),
blocContext: AdsBlocContext());
});
//------------------------------------------------------------------------------
//TAB BAR-----------------------------------------------------------------------
injector.map<TabModuleBuilder>((i) => () {
Widget profileTab = i.get<ProfileModuleBuilder>()();
Widget searchTab = i.get<SearchModuleBuilder>()();
Widget adsTab = i.get<AdsModuleBuilder>()();
return TabContainerScreen(
content: TabContainerContent(
firstTab: profileTab, secondTab: searchTab, thirdTab: adsTab),
);
});
//------------------------------------------------------------------------------
}
}
Комментарии:
1. Похоже, информации недостаточно. Не могли бы вы предоставить нам больше кода, чтобы мы могли воспроизвести проблему?
2. Для вашего
outerUserState
попробуйте вернуться.stream.asBroadcastStream()
. Подробнее о asBroadcastStream3. Хорошо, позвольте мне понять ваш вариант использования. Есть ли какая-то особая причина, по которой вы используете
StreamController
s в своемBLoC
s?4. К сожалению, это не помогло. Могу ли я как-то удалить все потоки прошлого при переключении в другое окно, а затем создать их снова?
5. Мне очень понравилась эта архитектура. И я хотел бы понять причину повторной подписки в моем пользовательском интерфейсе
Ответ №1:
У меня была такая же ошибка в моем проекте.
В моем случае мне нужно было повторно использовать StreamController. Сценарий заключается в том, что есть кнопка сброса для поиска информации с самого начала.
в любом случае я решил это следующим образом.
- добавить
.close()
к кнопке сброса. - снова инициализируйте StreamController в поиске.
if (streamController.isCloded) {
streamController = new StreamController<List<A>>();
}
В вашем случае, я думаю, что tabview восстанавливается (или перестраивается?) представление.
Итак, StreamController повторно инициализируется.
Я думаю, вы бы добавили инициализацию StreamController в initState.
@override
void initState(){
super.initState();
if (streamController == null){
streamController = new StreamController();
}
Я надеюсь, что это поможет вам.