Событие блока срабатывает только один раз
Я использовал этот подход для управления блоками и сохранения некоторой строки кодов.
Я хочу получить данные из своего API. Я хотел бы сделать это черезinitState
метод и вызов myBloc.add(MyEvent())
. Но проблема в том, что позвонили только один раз.
Я искал в Google и пробовал какое-то решение в нескольких блогах, и это официальные проблемы с репозиторием Github, но все еще не работает. Я нашел аналогичный вопрос, но, поскольку я не использую инъекцию зависимостей или синглтон, я не смог найти, что именно и где проблема, и моя проблема все еще не решена.
Вот что я пробовал до сих пор и все еще не решил проблему:
- Удаление Equatable из блока
- Жесткая перезагрузка устройства
- Бегать
flutter clean
команда - Повторно запустите приложение
Чтобы было понятно, посмотрите эту запись.
И, наконец, вот как выглядит мой скрипт:
leave_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter_prismahr/app/data/models/leave_model.dart';
import 'package:flutter_prismahr/app/data/repositories/leave_repository.dart';
import 'package:meta/meta.dart';
part 'leave_event.dart';
part 'leave_state.dart';
class LeaveBloc extends Bloc<LeaveEvent, LeaveState> {
LeaveBloc() : super(LeaveInitial());
final LeaveRepository repository = LeaveRepository();
@override
Stream<LeaveState> mapEventToState(
LeaveEvent event,
) async* {
print('TRIGGERED EVENT IS: $event');
if (event is LeaveScreenInitialized) {
yield LeaveLoading();
try {
final response = await repository.fetch();
if (response is List<Leave> && response.isNotEmpty) {
yield LeaveLoaded(data: response);
} else {
yield LeaveEmpty();
}
} catch (e) {
yield LeaveFailure(error: e.toString());
}
}
if (event is LeaveAdded) {
yield LeaveCreated(data: event.data);
}
}
}
leave_event.dart
part of 'leave_bloc.dart';
abstract class LeaveEvent {
const LeaveEvent();
// @override
// List<Object> get props => [];
}
class LeaveScreenInitialized extends LeaveEvent {}
class LeaveAdded extends LeaveEvent {
final Leave data;
const LeaveAdded({@required this.data}) : assert(data != null);
// @override
// List<Object> get props => [data];
@override
String toString() => 'LeaveAdded { data: $data }';
}
leave_state.dart
part of 'leave_bloc.dart';
abstract class LeaveState {
const LeaveState();
// @override
// List<Object> get props => [];
}
class LeaveInitial extends LeaveState {}
class LeaveLoading extends LeaveState {}
class LeaveEmpty extends LeaveState {}
class LeaveLoaded extends LeaveState {
final List<Leave> data;
const LeaveLoaded({@required this.data}) : assert(data != null);
// @override
// List<Object> get props => [data];
@override
String toString() => 'LeaveLoaded { data: $data }';
}
class LeaveFailure extends LeaveState {
final String error;
const LeaveFailure({@required this.error}) : assert(error != null);
// @override
// List<Object> get props => [error];
@override
String toString() => 'LeaveFailure { error: $error }';
}
class LeaveCreated extends LeaveState {
final Leave data;
const LeaveCreated({@required this.data}) : assert(data != null);
// @override
// List<Object> get props => [data];
@override
String toString() => 'LeaveCreated { data: $data }';
}
leave_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_prismahr/app/bloc/leave/leave_bloc.dart';
import 'package:flutter_prismahr/app/bloc/leave_update/leave_update_bloc.dart';
import 'package:flutter_prismahr/app/components/empty.dart';
import 'package:flutter_prismahr/app/data/models/leave_model.dart';
import 'package:flutter_prismahr/app/routes/routes.dart';
import 'components/leave_list.dart';
import 'components/leave_list_loading.dart';
class LeaveScreen extends StatefulWidget {
LeaveScreen({Key key}) : super(key: key);
@override
_LeaveScreenState createState() => _LeaveScreenState();
}
class _LeaveScreenState extends State<LeaveScreen> {
LeaveBloc _leaveBloc;
LeaveUpdateBloc _leaveUpdateBloc;
List<Leave> _leaves;
@override
void initState() {
print('INIT STATE CALLED');
_leaves = <Leave>[];
_leaveBloc = BlocProvider.of<LeaveBloc>(context);
_leaveUpdateBloc = BlocProvider.of<LeaveUpdateBloc>(context);
_leaveBloc.add(LeaveScreenInitialized());
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
floating: true,
title: Text(
'Leave Requests',
style: Theme.of(context)
.textTheme
.headline6
.copyWith(fontWeight: FontWeight.w900),
),
),
SliverToBoxAdapter(
child: MultiBlocListener(
listeners: [
BlocListener<LeaveBloc, LeaveState>(
listener: (context, state) {
if (state is LeaveLoaded) {
setState(() {
_leaves = state.data;
});
}
if (state is LeaveCreated) {
setState(() {
_leaves.add(state.data);
});
}
},
),
BlocListener<LeaveUpdateBloc, LeaveUpdateState>(
listener: (context, state) {
if (state is LeaveUpdateSuccess) {
int index = _leaves.indexWhere((leave) {
return leave.id == state.data.id;
});
setState(() {
_leaves[index] = state.data;
_leaveUpdateBloc.add(ResetState());
});
}
},
),
],
child: BlocBuilder<LeaveBloc, LeaveState>(
builder: (context, state) {
if (state is LeaveLoading) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 30,
),
child: LeaveListLoading(),
);
}
if (state is LeaveEmpty) {
return Padding(
padding: const EdgeInsets.only(top: 100),
child: Empty(),
);
}
return LeaveList(
data: _leaves,
bloc: _leaveUpdateBloc,
);
},
),
),
),
],
),
floatingActionButton: BlocBuilder<LeaveBloc, LeaveState>(
builder: (context, state) {
if (state is! LeaveLoading) {
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () async {
final data = await Navigator.of(context).pushNamed(
Routes.LEAVE_CREATE,
);
if (data != null) {
_leaveBloc.add(LeaveAdded(data: data));
}
},
);
}
return SizedBox();
},
),
);
}
}
app_router.dart
...
...
import 'package:flutter_prismahr/app/views/leave/leave_screen.dart';
...
...
class Router {
// Provide a function to handle named routes. Use this function to
// identify the named route being pushed, and create the correct
// screen.
final LeaveBloc _leaveBloc = LeaveBloc();
final LeaveUpdateBloc _leaveUpdateBloc = LeaveUpdateBloc();
final LeaveCreateBloc _leaveCreateBloc = LeaveCreateBloc();
Route<dynamic> generateRoute(RouteSettings settings) {
final RouteArguments args = settings.arguments;
switch (settings.name) {
...
...
case Routes.LEAVE:
return MaterialPageRoute(
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider(create: (context) => _leaveBloc),
BlocProvider(create: (context) => _leaveUpdateBloc),
BlocProvider(create: (context) => _leaveCreateBloc),
],
child: LeaveScreen(),
),
);
...
...
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}'),
),
),
);
}
}
void dispose() {
_leaveBloc.close();
_leaveUpdateBloc.close();
_leaveCreateBloc.close();
}
}
Любая подсказка??
1 ответ
TL;DR
- Не использовать
BlocProvider(create: (context) => _yourBloc)
по блочному доступу на уровне маршрута.- Использовать
BlocProvider.value(value: _leaveBloc, child: LeaveScreen())
вместо.- Не закрывайте блок внутри пользовательского интерфейса
dispose
метод.
С помощью @Rolly нам удалось решить основную причину этой проблемы. Это произошло потому, что в данном случае я использую поставщика доступа к блоку на уровне маршрута и закрываю блок из
dispose
в пользовательском интерфейсе. Поскольку это была проблема давным-давно, я не уверен, что все помню. Но я постараюсь объяснить, насколько это возможно и насколько я помню.
ГЛАВНАЯ ПРИЧИНА
Думайте о блоке как о двери. Я говорю своему приложению войти в
leaveBloc
на
LeaveScreen
когда пользователь получил к нему доступ, сделайте беспорядок и дайте им то, что им нужно. При первом обращении к нему он работает, потому что он был открыт, приложение знает, что делать, пока пользователь не нажмет кнопку "Назад" и мой скрипт не закроет его.
Когда пользователь возвращается на эту страницу, блок был закрыт, приложение пытается выбить дверь, но она не открывается, поэтому он не знает, что делать, а стоит там, ожидая открытия двери.
РЕШЕНИЕ
Поскольку мы используем доступ к блокам на уровне маршрута, приложение не должно закрывать какие-либо блоки внутри уровня пользовательского интерфейса. Вместо этого создайте функцию удаления в файле маршрута и активируйте ее в верхней части дерева виджетов. Вот так:
router.dart
class Router {
final LeaveBloc _leaveBloc = LeaveBloc(
...
);
Route<dynamic> generateRoute(RouteSettings settings) {
case Routes.LEAVE:
return MaterialPageRoute(
builder: (_) =>
BlocProvider.value(value: _leaveBloc, child: LeaveScreen()));
}
void dispose() {
_leaveBloc.close();
// another bloc
// another bloc
}
}
main.dart
void main() async {
...
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final Router _router = Router();
...
@override
void dispose() {
_router.dispose(); // <-- trigger dispose when the user closes the app
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
...
);
}
}
ДОПОЛНИТЕЛЬНОЕ ПРИМЕЧАНИЕ
Обратите внимание, что я меняю
route
сценарий из этого:
case Routes.LEAVE:
return MaterialPageRoute(
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider(create: (context) => _leaveBloc),
BlocProvider(create: (context) => _leaveUpdateBloc),
BlocProvider(create: (context) => _leaveCreateBloc),
],
child: LeaveScreen(),
),
);
К этому:
case Routes.LEAVE:
return MaterialPageRoute(
builder: (_) =>
BlocProvider.value(value: _leaveBloc, child: LeaveScreen()));
Это было необходимо, потому что нам нужно сообщить приложению, что мы не хотим закрывать блоки на уровне пользовательского интерфейса, и позволить методу dispose маршрутизатора выполнить эту работу. Это было официально объяснено в документации библиотеки блока в этой строке.
Мы используем BlocProvider.value при предоставлении экземпляра CounterBloc маршрутам, потому что мы не хотим, чтобы BlocProvider обрабатывал удаление блока (поскольку за это отвечает _AppState).
Ссылка:официальная документация библиотеки bloc (Bloc Access - Named Route Access)
Это нормально, когда вас вызывают только один раз в initState
. Эта ловушка жизненного цикла выполняется только один раз при создании виджета.
Если вы хотите, чтобы он выполнялся каждый раз, когда вы переходите к экрану сведений, перейдите к новому экрану сведений, поскольку при этом каждый раз будет воссоздавать ваш виджет сведений и добавлять событие.