Evento di blocco attivato solo una volta
Ho seguito questo approccio per gestire i blocchi e salvare qualche riga di codice.
Voglio recuperare i dati dalla mia API. Vorrei farlo tramite initStatemetodo e chiamare myBloc.add(MyEvent()). Ma il problema è che è stato chiamato solo una volta.
Ho cercato su Google e provato qualche soluzione su diversi blog e si tratta di problemi di repository Github ufficiali, ma continua a non funzionare. Ho trovato una domanda simile ma dal momento che non sto usando alcuna iniezione di dipendenza o singleton, non sono riuscito a trovare esattamente cosa e dove si trova il problema e il mio problema non è ancora stato risolto.
Ecco cosa ho provato finora e ancora non ho risolto il problema:
- Rimozione di Equatable dal blocco
- Ricaricare a fondo il dispositivo
- Esegui
flutter cleancomando - Eseguire nuovamente l'app
Per chiarire, dai un'occhiata a questa registrazione.
E infine, ecco come appare il mio script:
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();
}
}
Qualche indizio??
Risposte
TL; DR
- Non utilizzare
BlocProvider(create: (context) => _yourBloc)per l'accesso a blocchi a livello di percorso.- Usa
BlocProvider.value(value: _leaveBloc, child: LeaveScreen())invece.- Non chiudere il blocco all'interno del
disposemetodo dell'interfaccia utente.
Con l'aiuto di @Rolly, siamo riusciti a risolvere la causa principale di questo problema. È successo perché in questo caso sto utilizzando il provider di accesso al blocco a livello di percorso e chiudendo il blocco dal disposemetodo sull'interfaccia utente. Dato che era un problema di molto tempo fa, non sono sicuro di ricordare tutto. Ma cercherò di spiegare come ha senso e come ricordo come posso.
CAUSA PRINCIPALE
Pensa al blocco come a una porta. Dico alla mia app di entrare quando leaveBlocl' LeaveScreenutente accede, fare un pasticcio e dare loro ciò di cui hanno bisogno. La prima volta che si accede, funziona perché era aperta, l'app sa cosa fare fino a quando l'utente non ha premuto il pulsante Indietro e il mio script lo chiude.
Quando l'utente sta tornando su quella pagina, il blocco era chiuso, l'app prova a bussare alla porta ma non si apre, quindi non sa cosa fare se non restare lì ad aspettare che la porta venga aperta.
SOLUZIONE
Poiché stiamo usando l'accesso al blocco a livello di route, l'app non deve chiudere nessuno dei blocchi all'interno del livello dell'interfaccia utente. Invece, crea una funzione dispose sul file di percorso e attivala vicino alla parte superiore dell'albero del widget. Così:
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(
...
);
}
}
NOTA AGGIUNTIVA
Si noti che sto cambiando lo routescript da questo:
case Routes.LEAVE:
return MaterialPageRoute(
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider(create: (context) => _leaveBloc),
BlocProvider(create: (context) => _leaveUpdateBloc),
BlocProvider(create: (context) => _leaveCreateBloc),
],
child: LeaveScreen(),
),
);
A questo:
case Routes.LEAVE:
return MaterialPageRoute(
builder: (_) =>
BlocProvider.value(value: _leaveBloc, child: LeaveScreen()));
Era necessario perché dobbiamo dire all'app che non vogliamo chiudere i blocchi dal livello dell'interfaccia utente e lasciare che il metodo di smaltimento del router faccia il lavoro. È stato spiegato ufficialmente nella documentazione della libreria dei blocchi in questa riga.
Utilizziamo BlocProvider.value quando forniamo l'istanza CounterBloc alle rotte perché non vogliamo che BlocProvider gestisca l'eliminazione del blocco (poiché _AppState ne è responsabile).
Riferimento: documentazione ufficiale della libreria dei blocchi (Bloc Access - Named Route Access)
È normale essere chiamati solo una volta nel file initState. L'hook del ciclo di vita viene eseguito solo una volta, quando viene creato il widget.
Se vuoi che venga eseguito ogni volta che accedi alla schermata dei dettagli, passa a una nuova schermata dei dettagli, poiché ciò ricreerà ogni volta il widget dei dettagli e aggiungerà il tuo evento.