Describe the bug
Our project use WebSocket and would like to fire an unsubscribe channel event to the server in dispose method but we discovered that when hot-restart. it didn't get called. This resulting in server overrun with duplicate connection id of the same users. (We temporary solved this by close the app in emulator first because the app also called unsub in pause and inactive state)
Maybe this is framework problem rather than bloc problem.
Expected behavior
When press hot restart dispose in bloc also get called.
Screenshots

Paste the output of running flutter doctor -v here.
[鉁揮 Flutter (Channel stable, v1.7.8+hotfix.4, on Mac OS X 10.14.6 18G87, locale en-TH)
[鉁揮 Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[鉁揮 Xcode - develop for iOS and macOS (Xcode 10.2.1)
[鉁揮 iOS tools - develop for iOS devices
[鉁揮 Android Studio (version 3.5)
[鉁揮 VS Code (version 1.37.1)
[鉁揮 Connected device (1 available)
Hi @criticalx7 馃憢
Thanks for opening an issue!
Can you please provide a small sample app which illustrates the problem you're having? It would be helpful to understand how you are instantiating the blocs 馃憤
Thanks!
this is the closest structure to our app.
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter Demo',
theme: ThemeData.dark(),
home: CounterPage(),
);
}
}
class CounterBloc extends Bloc<CounterEvent, int> {
@override
int get initialState => 0;
@override
Stream<int> mapEventToState(CounterEvent event) async* {
yield* _mapCounterEventToState(event);
}
Stream<int> _mapCounterEventToState(CounterEvent event) async* {
yield currentState + event.n;
}
@override
void dispose() {
console.log(
'i called my unsubscribe method which fire a websocket event to the server');
super.dispose();
}
}
class CounterEvent extends Equatable {
final int n;
CounterEvent(this.n);
}
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
CounterBloc counterBloc;
@override
void initState() {
super.initState();
counterBloc = CounterBloc();
}
@override
Widget build(BuildContext context) {
return MultiBlocProvider(providers: [
BlocProvider<CounterBloc>(
builder: (context) => counterBloc,
)
], child: CounterView());
}
}
class CounterView extends StatelessWidget {
const CounterView({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
appBar: AppBar(
title: Text('Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
BlocBuilder<CounterBloc, int>(
builder: (context, state) {
return Text(
'$state',
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
final counterBloc = BlocProvider.of<CounterBloc>(context);
counterBloc.dispatch(CounterEvent(1));
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
),
);
}
}
Thanks for the additional info @criticalx7 馃憤
One minor improvement you can make is refactoring your CounterPage to be a StatelessWidget like:
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(providers: [
BlocProvider<CounterBloc>(
builder: (context) => CounterBloc(),
)
], child: CounterView());
}
}
Other than that everything looks good. This is an issue with Flutter like you originally suggested. I confirmed that by putting together the following app:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHome(),
);
}
}
class MyHome extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
@override
void dispose() {
print('dispose');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Example'),
),
body: Container(),
);
}
}
On hot-restart, dispose is never called. I think this would be an interesting topic to bring up on flutter issues.
Hope that helps 馃憤
Thanks man. I make CounterPage stateful because in our app, we also add WidgetsBindingObserver to listen for a resume event. We need dispose method to cleanup properly.
Hi. I use one Bloc for two screen separately but when I use event in first page it works and this bloc doesnt dispose.When I use this bloc in second page show first page state
@umidshox99 the bloc is only disposed if you use BlocProvider to create it and the disposal happens when the BlocProvider is unmounted from the widget tree. Hope that helps 馃憤
@felangel thanks bro it helps
Most helpful comment
Thanks man. I make CounterPage stateful because in our app, we also add WidgetsBindingObserver to listen for a resume event. We need dispose method to cleanup properly.