Using bloc from rxdart: ^0.24.1
I am trying to save object on mysql
. The first try the object get saved succefully, the second try, with a new object, it falling on formKey.currentState.save()
. I am using GlobalKey<FormState>()
in order to validate the form with Stream
My code is
class DetailGamePage extends StatefulWidget {
@override
_DetailGameState createState() => _DetailGameState();
}
class _DetailGameState extends State<DetailGamePage> {
final formKey = GlobalKey<FormState>();
GameBloc gameBloc;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (gameBloc == null) {
gameBloc = Provider.gameBloc(context);
}
}
@override
Widget build(BuildContext context) {
Game _game = ModalRoute.of(context).settings.arguments;
if (_game == null) {
_game = Game(
color: "#000000",
description: "",
env: "",
isBuyIt: false,
isOnBacklog: false);
}
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.black),
backgroundColor: Colors.white,
title: Text(
"Add Game",
style: TextStyle(color: Colors.black),
),
actions: [
FlatButton(
onPressed: () {
if (formKey.currentState.validate()) {
formKey.currentState.save();
Fluttertoast.showToast(msg: "Game saved");
setState(() {
gameBloc.saveOrUpdate(_game, gameBloc.name,
gameBloc.description, "listGame");
});
Navigator.pushReplacementNamed(context, "home");
}
},
child: Text(
(StringUtils.isNullOrEmpty(_game.id)) ? "Add" : "Update",
style: TextStyle(color: HexColor(_game.color), fontSize: 20),
))
],
),
body: Form(
key: formKey,
child: Stack(children: <Widget>[
_createBackground(context, _game),
_createFormGame(context, _game, gameBloc)
]),
));
}
Widget _createBackground(BuildContext context, Game game) {
final size = MediaQuery.of(context).size;
final gradientTop = Container(
height: size.height, //* 0.4,
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: <Color>[HexColor(game.color), Colors.white])),
);
final circule = Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100.0),
color: Color.fromRGBO(255, 255, 255, 0.1)),
);
return Stack(
children: <Widget>[
gradientTop,
Positioned(
child: circule,
top: 90,
left: 50,
),
Positioned(
child: circule,
top: -40,
right: -30,
),
Container(
padding: EdgeInsets.only(top: 80),
child: Column(
children: <Widget>[
SizedBox(
height: 10.0,
width: double.infinity,
),
],
),
)
],
);
}
Widget _createFormGame(BuildContext context, Game game, GameBloc gameBloc) {
final size = MediaQuery.of(context).size;
return SingleChildScrollView(
child: Column(
children: <Widget>[
SafeArea(
child: Container(
height: 80.0,
)),
Container(
width: size.width * 0.85,
padding: EdgeInsets.symmetric(vertical: 50.0),
margin: EdgeInsets.symmetric(vertical: 30.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5.0),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black26,
blurRadius: 3.0,
offset: Offset(0.0, 5.0),
spreadRadius: 3.0)
]),
child: Column(
children: <Widget>[
Text("Foto", style: TextStyle(fontSize: 20.0)),
SizedBox(
height: 50.0,
),
_createNameImput(gameBloc, game),
_createDescriptionImput(gameBloc, game),
Divider(
height: 30,
color: HexColor(game.color),
indent: 30,
endIndent: 20,
),
_createWasGameImput(gameBloc, game),
Divider(
height: 30,
color: HexColor(game.color),
indent: 30,
endIndent: 20,
),
_createToTheBacklogImput(gameBloc, game),
SizedBox(height: 60),
_createDeleteButton(gameBloc, game),
SizedBox(height: 60),
],
))
],
),
);
}
@override
void dispose() {
gameBloc?.dispose();
super.dispose();
}
Widget _createWasGameImput(GameBloc gameBloc, Game game) {
return StreamBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: SwitchListTile(
activeColor: HexColor(game.color),
title: Text("Do you have it?"),
value: game.isBuyIt,
onChanged: (bool value) {
setState(() {
game.isBuyIt = value;
});
},
secondary: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: null,
color: HexColor(game.color),
),
));
},
);
}
Widget _createToTheBacklogImput(GameBloc gameBloc, Game game) {
return StreamBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: SwitchListTile(
activeColor: HexColor(game.color),
title: Text("To the backlog?"),
value: game.isOnBacklog,
onChanged: (bool value) {
setState(() {
game.isOnBacklog = true;
});
},
secondary: IconButton(
icon: Icon(Icons.list),
onPressed: null,
color: HexColor(game.color),
),
));
},
);
}
Widget _createNameImput(GameBloc gamebloc, Game game) {
return Column(children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
initialValue: game.name,
onSaved: (value) {
gameBloc.setName(value);
},
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: "Name",
icon: Icon(
Icons.games,
color: HexColor(game.color),
)),
),
),
Divider(
height: 30,
color: HexColor(game.color),
indent: 30,
endIndent: 20,
),
]);
}
Widget _createDescriptionImput(GameBloc gameBloc, Game game) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
initialValue: game.description,
onSaved: (value) {
gameBloc.setDescription(value);
},
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: "Description",
icon: Icon(
Icons.description,
color: HexColor(game.color),
)),
),
);
}
Widget _createDeleteButton(GameBloc gameBloc, Game game) {
if (StringUtils.isNotNullOrEmpty(game.id)) {
return FlatButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text("Do you wan to remove the game"),
actions: <Widget>[
FlatButton(
onPressed: () {
setState(() {
gameBloc.remove(game, "listGame");
});
Navigator.pop(context);
Navigator.pop(context);
},
child: Text("Yes")),
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text("No"))
],
);
});
},
child: Text("Remove Game"));
} else {
return Container();
}
}
}
This is the bloc
class GameBloc extends Validators {
//Controller
final _allDataGames = BehaviorSubject<List<Game>>();
final _descriptionController = BehaviorSubject<String>();
final _nameController = BehaviorSubject<String>();
final _allMyListGamesByNameController = BehaviorSubject<List<Game>>();
//Services
GameService gameService = GameService();
//get Data from streams
Stream<List<Game>> get allGameData => _allDataGames.stream;
Stream<List<Game>> get allGameByNameList =>
_allMyListGamesByNameController.stream;
Stream<String> get getDescriptionStream =>
_descriptionController.stream.transform(validateDescription);
Stream<String> get getNameStream =>
_nameController.stream.transform(validName);
//Observable
Stream<bool> get validateDescriptionStream =>
Rx.combineLatest([getDescriptionStream], (description) => true);
Stream<bool> get validateNameStream =>
Rx.combineLatest([getNameStream], (name) => true);
//Set Stream
Function(String) get setDescription => _descriptionController.sink.add;
Function(String) get setName => _nameController.sink.add;
//Get Stream
//From repo
void allGames() async {
List<Game> games = await gameService.getAllDataGames();
_allDataGames.sink.add(games);
}
//From my setting
void allMyListGamesByName(String listName) async {
List<Game> games = await gameService.allMyListGamesByName(listName);
_allMyListGamesByNameController.sink.add(games);
}
void saveOrUpdate(
Game game, String name, String description, String listGame) {
game.name = name;
game.description = description;
if (StringUtils.isNullOrEmpty(game.id)) {
game.id = Uuid().v1();
gameService.add(game, listGame);
} else {
gameService.update(game);
}
}
void remove(Game game, String listGame) {
gameService.remove(game, listGame);
}
//Get Lastest stream value
String get name => _nameController.value;
String get description => _descriptionController.value;
dispose() {
_descriptionController?.close();
_allMyListGamesByNameController?.close();
_allDataGames?.close();
_nameController?.close();
}
}
The provider:
class Provider extends InheritedWidget {
static Provider _imstance;
final _gameBloc = GameBloc();
factory Provider({Key key, Widget child}) {
if (_imstance == null) {
_imstance = new Provider._internal(key: key, child: child);
}
return _imstance;
}
Provider._internal({Key key, Widget child}) : super(key: key, child: child);
static GameBloc gameBloc(BuildContext context) {
return (context.inheritFromWidgetOfExactType(Provider) as Provider)
._gameBloc;
}
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
}
The error is:
════════ Exception caught by gesture ═══════════════════════════════════════════
Bad state: Cannot add new events after calling close
When I evaluate formKey.currentState.save();
I got:
formKey.currentState.save()
Unhandled exception:
Bad state: Cannot add new events after calling close
#0 _BroadcastStreamController.add (dart:async/broadcast_stream_controller.dart:249:24)
#1 Subject._add (package:rxdart/src/subjects/subject.dart:141:17)
#2 Subject.add (package:rxdart/src/subjects/subject.dart:135:5)
#3 _StreamSinkWrapper.add (package:rxdart/src/subjects/subject.dart:167:13)
I was reading about this error, it mention the error is on Bloc singleston scope
or dispose
method.
What is happen?
When you navigate to home with
Navigator.pushReplacementNamed(context, "home")
, the_DetailGamePage<State>
is being disposed, callinggameBloc?.dispose
. This leaves_gameBloc
instantiated with all streams closed.As you are using a Singleton Provider, when you navigate back to
DetailGamePage
, your save is trying to write to the closed streams.What you need to do is move the closure of the streams farther up the widget tree so as not to close them before you are done with them, perhaps at the app level OR re-instantiate
_gameBloc
if the streams are closed, loading the data from the repo again.