Reputation: 2248
I followed this approach to manage blocs and save some line of codes.
I want to fetch data from my API. I would like to do it via initState
method and call myBloc.add(MyEvent())
. But the problem is, it was only called once.
I've googled around and tried some solution on several blogs and it's official Github repo issues but still doesn't work. I found a similar question but since I'm not using any dependency injection or singleton, I couldn't find what exactly and where the problem is and my problem is still not fixed.
Here is what i've tried so far and still not solving the problem:
flutter clean
commandTo make it clear please have a look at this recording.
And lastly, here is how my script looks like:
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();
}
}
Any clue??
Upvotes: 4
Views: 6056
Reputation: 2248
TL;DR
- Do not use
BlocProvider(create: (context) => _yourBloc)
on route-level bloc access.- Use
BlocProvider.value(value: _leaveBloc, child: LeaveScreen())
instead.- Do not close the bloc inside UI's
dispose
method.
With help from @Rolly, We've managed to solve the main cause of this issue. It happened because in this case, I'm using route level bloc access provider and closing the bloc from dispose
method on the UI. As it was an issue from a long time ago, I'm not sure I remember everything. But I'll try to explain as make sense and as I remember as I can.
MAIN CAUSE
Think about the bloc as a door. I tell my app to get in to leaveBloc
on LeaveScreen
when user accessed it, do a mess, and give them what they need. First time it being accessed, it works because it was open, the app knows what to do until the user pressed back button and my script closes it.
When the user is going back to that page, the bloc was closed, the app tries to knock the door but it won't open, so it doesn't know what to do but standing there waiting for the door to be opened.
SOLUTION
Because we are using route-level bloc access, the app should not close any of the blocs inside the UI level. Instead, create a dispose function on the route file and trigger it near the top of your widget tree. Like so:
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(
...
);
}
}
ADDITIONAL NOTE
Notice that I'm changing the route
script from this:
case Routes.LEAVE:
return MaterialPageRoute(
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider(create: (context) => _leaveBloc),
BlocProvider(create: (context) => _leaveUpdateBloc),
BlocProvider(create: (context) => _leaveCreateBloc),
],
child: LeaveScreen(),
),
);
To this:
case Routes.LEAVE:
return MaterialPageRoute(
builder: (_) =>
BlocProvider.value(value: _leaveBloc, child: LeaveScreen()));
It was necessary because we need to tell the app that we don't want to close the blocs from the UI level and let the router's dispose method do the job. It was officially explained in the bloc library's documentation in this line.
We're using BlocProvider.value when providing the CounterBloc instance to the routes because we don't want the BlocProvider to handle disposing the bloc (since _AppState is responsible for that).
Reference: bloc library's official documentation (Bloc Access - Named Route Access)
Upvotes: 15
Reputation: 173
It's normal to only be called once in the initState
. That lifecycle hook is only executed once, when the widget is created.
If you want it to be executed every single time you navigate to the details screen, then navigate to a new details screen, since that will re-create your details widget each time and add your event.
Upvotes: 1