Flutter async await skiping to next function - flutter

Trying to make a button which replace text with circlularprogressIndication. when task or function done and not working properly
is there any function called Whencompleted or something pls susggest
class Continue extends StatefulWidget {
const Continue({Key? key, required this.ontap}) : super(key: key);
final Function ontap;
#override
State<Continue> createState() => _ContinueState();
}
class _ContinueState extends State<Continue> {
bool loading = false;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () async {
setState(() {
loading = true;
});
await widget.ontap();
setState(() {
loading = false;
});
},
child: Container(
width: 180.w,
height: 50.h,
decoration: BoxDecoration(
color: Theme.of(context).primaryColorDark,
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
child: Padding(
padding: EdgeInsets.all(8.sp),
child: Center(
child: loading == true
? CircularProgressIndicator(
color: Theme.of(context).primaryColor,
)
: ResponsiveText(
text: 'Continue',
maxLines: 1,
centered: true,
size: 16,
style: TextStyle(color: Theme.of(context).primaryColor),
),
),
),
),
);
}
}
And my ontap function is:
ontap:() {
Timer(Duration(seconds: 3), () {
setState(() {
otpActive = true;
});
});
},

Function is not async, if you want to do something in async you should use Future so change to this:
final Future<void> Function() ontap;
and also in your ontap do this:
ontap: ()async{
await Future.delayed(Duration(seconds: 3));
setState(() {
otpActive = true;
});
},

Just use Future.delayed instead of Timer in your OnTap function:
() async {
await Future.delayed(Duration(seconds: 3), () {
setState(() {
otpActive = true;
});
});
}

Related

Unhandled Exception: setState() called after dispose() - due to modal dissmissed

I have a modalBottomSheet. On it, I am displaying several widgets, especially an audioPlayer.
I have find out that when I press the play button, the audio file is playing, so far so good, but if I tap outside the modalBottomSheet, the modal is dismissed. I am OK to get the modal dismissed. But my problem is that when it is dismissed, the player which is running, is generating an exception.
Unhandled Exception: setState() called after dispose()
I do not want to make the Modal not dissmisible.
Please, can you advise? Many thanks.
import 'package:audioplayers/audioplayers.dart';
import 'package:audioplayers/audioplayers_api.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gtd_official_sharped_focused/Reusable%20Widget/Player_Audio/widgets/play_pause_button.dart';
class AudioPlayerWidget extends StatefulWidget {
final String url;
final bool isAsset;
final Duration currentTime;
final Duration totalTime;
final ValueChanged<Duration> onSeekBarMoved;
const AudioPlayerWidget({
Key key,
this.url,
this.isAsset = false,
this.currentTime,
this.totalTime,
this.onSeekBarMoved,
}) : super(key: key);
#override
_AudioPlayerWidgetState createState() => _AudioPlayerWidgetState();
}
class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
AudioPlayer _audioPlayer;
AudioCache _audioCache;
//variables for slider
Duration _duration = new Duration();
Duration _position = new Duration();
PlayerState _playerState = PlayerState.STOPPED;
bool get _isPlaying => _playerState == PlayerState.PLAYING;
bool get _isLocal => !widget.url.contains('https');
#override
void initState() {
_audioPlayer = AudioPlayer(mode: PlayerMode.MEDIA_PLAYER);
_audioCache = AudioCache(fixedPlayer: _audioPlayer);
_audioPlayer.onDurationChanged.listen((d) {setState(() {
_duration = d;
});});
_audioPlayer.onAudioPositionChanged.listen((p) {setState((){
_position = p;
});});
_audioPlayer.onPlayerCompletion.listen((event) {
setState(() {
_position = Duration(seconds: 0);
_playerState = PlayerState.STOPPED;
});
});
_audioPlayer.onPlayerError.listen((msg) {
print('audioPlayer error : $msg');
setState(() {
_playerState = PlayerState.STOPPED;
});
});
super.initState();
}
#override
void dispose() {
_audioPlayer.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left:18.0),
child: Text(_position.toString().split('.')[0],style:TextStyle(fontSize: 16)),
),
Padding(
padding: const EdgeInsets.only(right:18.0),
child: Text(_duration.toString().split('.')[0],style:TextStyle(fontSize: 16)),
),
],),
_buildSliderBar(context),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
buttonBackWard10Seconds(),
PlayPauseButton(
isPlaying: _isPlaying,
onPlay: () => _playPause()
),
buttonForward10Seconds(),
//Do not delete iconButton below => for reference
/* IconButton(
onPressed: () => _stop(),
icon: Icon(
Icons.stop,
size: 40,
color: Colors.red,
),
),*/
],
),
],
);
}
//########################################################
_playPause() async {
if (_playerState == PlayerState.PLAYING) {
final playerResult = await _audioPlayer.pause();
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.PAUSED;
});
}
} else if (_playerState == PlayerState.PAUSED) {
final playerResult = await _audioPlayer.resume();
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.PLAYING;
});
}
} else {
if (widget.isAsset) {
_audioPlayer = await _audioCache.play(widget.url);
setState(() {
_playerState = PlayerState.PLAYING;
});
} else {
final playerResult = await _audioPlayer.play(widget.url, isLocal: _isLocal);
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.PLAYING;
});
}
}
}
}
void changeToSecond(int second){
Duration newDuration = Duration(seconds:second);
_audioPlayer.seek(newDuration);
}
_stop() async {
final playerResult = await _audioPlayer.stop();
if (playerResult == 1) {
setState(() {
_playerState = PlayerState.STOPPED;
});
}
}
//###############################################################
Slider _buildSliderBar(BuildContext context) {
return Slider(
value: _position.inSeconds.toDouble(),
min: 0.0,
max: _duration.inSeconds.toDouble(), //_sliderValue,
activeColor: Colors.red,
inactiveColor: Colors.grey,
onChanged: (double value) {
setState(() {
changeToSecond(value.toInt());
value=value;
});
},
);
}
Widget buttonBackWard10Seconds(){
return IconButton( icon: Icon(CupertinoIcons.gobackward_10),
iconSize: 40,
color: Colors.black,
onPressed: (){
_position = _position - Duration(seconds:10);
if (_position < Duration(seconds:0)) {
_audioPlayer.seek(Duration(seconds: 0));
}
else {
_audioPlayer.seek(_position);
}});
}
Widget buttonForward10Seconds(){
return IconButton( icon:Icon( CupertinoIcons.goforward_10),
iconSize: 40,
color: Colors.black,
onPressed: (){
_position = _position + Duration(seconds:10);
if (_duration >_position) {
_audioPlayer.seek(_position);
}
else if (_duration <_position) {
_audioPlayer.seek(_duration);
}
}
);
}
}
import 'package:flutter/material.dart';
import 'package:gtd_official_sharped_focused/Reusable%20Widget/Player_Audio/widgets/audio_player_widget.dart';
import 'package:gtd_official_sharped_focused/Services/extract_file_Name_url/extract_file_name_url.dart';
Widget modalBottomPlayAudio (context,String urlToPlay){
showModalBottomSheet(
context: context,
//background color for modal bottom screen
backgroundColor: Colors.white,
//elevates modal bottom screen
elevation: 10,
// gives rounded corner to modal bottom screen
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
builder: (BuildContext context) {
// UDE : SizedBox instead of Container for whitespaces
return SizedBox(
height: 350,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
/*Padding(
padding: const EdgeInsets.all(28.0),
),*/
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left:18.0),
child: Text(getFileNameFromURL(urlToPlay),
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
),
],
),
SizedBox(height: 60,),
AudioPlayerWidget(url:urlToPlay),
],
),
),
);
},
);
}
you could change
setState(()=>{
...
})
to
if(mounted)(
setState(()=>{
...
})
)
Which ensures setState is called only when the widget is mounted on the screen.

how to add loader when on the toggle button

I want to display a circular loader when user is going to on the toggle button, then after few secs toggle button will active.
here is my code
InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProductProfile(),
));
},
child: Container(
decoration: BoxDecoration(
color: _selectedProducts.contains(book.id) ? Colors.grey[200] :Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
child: ListTile(
dense: true,
trailing: Switch(
value: _selectedProducts.contains(book.id),
onChanged: (bool? selected) {
if (selected != null) {
setState(() {
_onProductSelected(selected, book.id);
});
}
},
activeTrackColor: HexColor("#b8c2cc"),
activeColor: HexColor("#7367f0"),
),
title: Text(
book.title,),
Divider()
),
),
),
SizedBox10(),
],
);
please help how to do this
To achieve that, you need bool _isLoading and a timer. Steps I would do:
Declare _isLoading: bool _isLoading = false;
Change _isLoading value using a timer:
void timer() {
int _time = 10;
Timer timer = new Timer.periodic(
Duration(seconds: 1),
(Timer timer) async {
if (_time == 0) {
_isLoading = true;
timer.cancel();
} else {
setState(() {
_time--;
});
}
},
);
}
Use _isLoading on your build method (for example):
#override
Widget build(BuildContext context) {
return _isLoading ? CircularProgressIndicator() : Container();
}
Or to hide your button:
#override
Widget build(BuildContext context) {
return _isLoading ? Container() : YourToggleButton;
}
Also remember to dispose your timer!
#override
void dispose() {
timer.cancel();
}
So, If you are on Flutter web, there is a widget called MouseRegion which has onHover, onEnter & onExit.
You can assign a new bool for instance bool showLoader=false which you will toggle to true with setState (inside the onHover, where you could also start the Timer and when finished reset the showLoader to false).
Now you can show your button with a ternary operator : showLoader ? CircularProgressIndicator() : YourButton()

Flutter dont reload a image when i take a picture

i'm studying flutter and im trying to create a person register. In this page i hava a normal register with basic data and person photo.
But im having some issue with update a Image, when i take the picture the component Image dont change to taked image;
I tried find some controller to Image or Image Provider but unsuccessful.
This is my code:
class _RegisterForm extends State<RegisterForm> {
final TextEditingController _textEditingControllerPassword =
new TextEditingController();
final TextEditingController _textEditingControllerComparePassword =
new TextEditingController();
final TextEditingController _textEditingControllerEmail =
new TextEditingController();
final TextEditingController _textEditingControllerFullName =
new TextEditingController();
final _formKey = GlobalKey<FormState>();
User _user = null;
Image _image = null;
#override
void setState(VoidCallback fn) {
super.initState();
this._user = new User(
_textEditingControllerPassword.text,
_textEditingControllerFullName.text,
_textEditingControllerEmail.text,
_textEditingControllerComparePassword.text,
_image,
);
this.build(context);
}
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.fromLTRB(23, 0, 23, 0),
child: Column(children: <Widget>[
GestureDetector(
child: CircleAvatarImage(_image),
onTap: getImage,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Tire sua foto!"),
),
InputString(
"Como gostaria ",
hint: "Marianx Silva",
maxLenght: 100,
controller: this._textEditingControllerFullName,
onChanged: (context) => {
setState(() {
_user = null;
}),
},
),
InputString(
"E-mail",
maxLenght: 100,
controller: this._textEditingControllerEmail,
onChanged: (context) => {
setState(() {
_user = null;
}),
},
),
InputString(
"Senha",
controller: this._textEditingControllerPassword,
maxLenght: 12,
obscureText: true,
onChanged: (context) => {
setState(() {
_user = null;
}),
},
),
InputString(
"Confime a senha",
controller: this._textEditingControllerComparePassword,
maxLenght: 12,
obscureText: true,
onChanged: (context) => {
setState(() {
_user = null;
}),
},
),
RegisterUserInFirebaseButton(
"Continuar",
_formKey,
_user,
// edgeInsets: EdgeInsets.fromLTRB(0, 40, 0, 0),
),
]),
));
}
Future getImage() async {
File image = await ImagePicker.pickImage(source: ImageSource.camera);
this._image = Image.file(image);
setState(() {
_user = null;
});
}
}
This is my "CircleAvatarImage" component:
import 'package:flutter/material.dart';
const AssetImage _defaultImage = AssetImage("assets/images/default_image.png");
class CircleAvatarImage extends StatefulWidget {
Image image;
CircleAvatarImage(this.image);
#override
State<StatefulWidget> createState() {
return __CircleAvatarImageState();
}
}
class __CircleAvatarImageState extends State<CircleAvatarImage> {
AnimationController _controller;
#override
Widget build(BuildContext context) {
return CircleAvatar(
radius: 62,
backgroundColor: Colors.blue,
child: CircleAvatar(
radius: 60.0,
child: Padding(
padding: const EdgeInsets.only(top: 90),
child: Icon(
Icons.camera_enhance,
color: Colors.white,
),
),
backgroundImage:
widget.image == null ? _defaultImage : widget.image.image,
),
);
}
}
You didn't use setState. So, there's no way Widget would no that it has to udpate. Try :
setState() => this._image = Image.file(image);

how to refresh state on Navigator.Pop or Push in flutter

Here I have two pages first is called BSP_signup_terms page and the second is Bsp_Service_page. when I am on BSP_signup_terms on that page I have to select some checkbox based on the selected checkbox it will show me some data. but problem is that it will show me the complete data but when I get back to the BSP_signup_terms from Bsp_signup_page and I am changing the checkbox and then again when I am pressing next button it will not change the result it same as the previous result.
Here is the Image of Output Page
In this image I've attached both screen output when I am selecting only one checkbox it will render some value in service page and when I am back to the Terms and Condition page and select one more checkbox then it will not updating service page
Here is the code I've tried.
BSP_Signup_Terms_Page
class BspLicensedSignupTermsPage extends StatefulWidget {
static const String routeName = "/bspLicensedSignupTerms";
final BspSignupCommonModel bspSignupCommonModel;
BspLicensedSignupTermsPage({
Key key,
#required this.bspSignupCommonModel,
}) : super(key: key);
#override
_BspLicensedSignupTermsPageState createState() =>
_BspLicensedSignupTermsPageState();
}
class _BspLicensedSignupTermsPageState
extends State<BspLicensedSignupTermsPage> {
#override
void initState() {
super.initState();
}
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
bool _isWalkIn = false;
bool _isHome = false;
bool _isOnDemand = false;
Widget _buildselectcheckbox() {
return Text(
AppConstantsValue.appConst['bsplicensedsignupterms']['selectcheck']
['translation'],
);
}
// Walkin
_onCustomerWalkin(value) {
setState(() {
_isWalkIn = value;
});
}
Widget _buildCustomerWalkIn() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['CustomerWalkIn']['translation'],
onChanged: (value) {
print(value);
_onCustomerWalkin(value);
},
validate: false,
);
}
// Home
_onCustomerInHome(value) {
setState(() {
_isHome = value;
});
}
Widget _buildCustomerInHome() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['CustomerInHome']['translation'],
onChanged: (value) {
_onCustomerInHome(value);
},
validate: false,
);
}
Widget _buildCustomerInHomeHelp() {
return Text(
AppConstantsValue.appConst['bsplicensedsignupterms']['businesscheckhelp']
['translation'],
);
}
// On Demand
_onCustomerOnDemand(value) {
setState(() {
_isOnDemand = value;
});
}
Widget _buildBusinessOnDemand() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['BusinessOnDemand']['translation'],
onChanged: (value) {
_onCustomerOnDemand(value);
},
validate: false,
);
}
Widget _buildBusinessOnDemandHelp() {
return Text(AppConstantsValue.appConst['bsplicensedsignupterms']
['businessprovidehelp']['translation']);
}
#override
Widget build(BuildContext context) {
final appBar = AppBar(
title: Text("Bsp Licensed Signup Terms and Condition"),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
NavigationHelper.navigatetoBack(context);
},
),
centerTitle: true,
);
final bottomNavigationBar = Container(
height: 56,
//margin: EdgeInsets.symmetric(vertical: 24, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FlatButton.icon(
icon: Icon(Icons.close),
label: Text('Clear'),
color: Colors.redAccent,
textColor: Colors.black,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
_formKey.currentState.reset();
},
),
new FlatButton.icon(
icon: Icon(FontAwesomeIcons.arrowCircleRight),
label: Text('Next'),
color: colorStyles["primary"],
textColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
if (_formKey.currentState.validate()) {
if (_isHome == false &&
_isOnDemand == false &&
_isWalkIn == false) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => ShowErrorDialog(
title: Text('Select Service'),
content: Text(
'Please select atleast one service type to proceed next',
),
));
} else {
BspSignupCommonModel model = widget.bspSignupCommonModel;
model.isWalkin = _isWalkIn;
model.isHome = _isHome;
model.isOnDemand = _isOnDemand;
print(model.toJson());
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
BspServicePage(bspSignupCommonModel: model),
),
);
}
}
},
),
],
),
);
return new Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: Container(
height: double.infinity,
width: double.infinity,
child: Stack(
children: <Widget>[
SingleChildScrollView(
child: SafeArea(
child: Form(
autovalidate: true,
key: _formKey,
child: Scrollbar(
child: SingleChildScrollView(
dragStartBehavior: DragStartBehavior.down,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: new Container(
decoration: BoxDecoration(
borderRadius: new BorderRadius.circular(25)),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildselectcheckbox(),
_buildCustomerWalkIn(),
_buildCustomerInHome(),
_buildCustomerInHomeHelp(),
_buildBusinessOnDemand(),
_buildBusinessOnDemandHelp(),
],
),
),
),
),
),
),
),
],
),
),
);
}
}
BSP_Service_Page
class BspServicePage extends StatefulWidget {
static const String routeName = "/bspService";
final BspSignupCommonModel bspSignupCommonModel;
BspServicePage({
Key key,
#required this.bspSignupCommonModel,
}) : super(key: key);
#override
_BspServicePageState createState() => _BspServicePageState();
}
class _BspServicePageState extends State<BspServicePage> {
List<int> servicesIds = [];
Map<String, bool> selection = {};
List<BspServices.Service> selectedServices = [];
SearchBarController _controller = new SearchBarController();
String _searchText = '';
bool refreshservices = true;
#override
void initState() {
super.initState();
}
void _showErrorDialog(String message) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => ShowErrorDialog(
title: Text('An Error Occurred!'),
content: Text(message),
),
);
}
void refresh() {
setState(() {
refreshservices = !refreshservices;
});
}
#override
Widget build(BuildContext context) {
var _bspServiceBloc = new BspServiceBloc();
final appBar = SearchBar(
controller: _controller,
onQueryChanged: (String query) {
print('Search Query $query');
setState(() {
_searchText = query;
});
},
defaultBar: AppBar(
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
refresh();
NavigationHelper.navigatetoBack(context);
}),
title: Text('Select Services'),
),
);
final bottomNavigationBar = Container(
height: 56,
// margin: EdgeInsets.symmetric(vertical: 24, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FlatButton.icon(
icon: Icon(Icons.close),
label: Text('Clear'),
color: Colors.redAccent,
textColor: Colors.black,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
print('reseting the state');
setState(() {
selection = {};
servicesIds = [];
});
},
),
new FlatButton.icon(
icon: Icon(FontAwesomeIcons.arrowCircleRight),
label: Text('Next'),
color: colorStyles["primary"],
textColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
BspSignupCommonModel model = widget.bspSignupCommonModel;
model.servicesIds = servicesIds;
model.services = selectedServices;
print('servicesIds at the next button');
print(servicesIds);
print(model.toJson());
if (servicesIds.length == 0) {
_showErrorDialog(
'You need to select at least one service to proceed next!');
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BusinessProfilePage(
bspSignupCommonModel: model,
),
),
);
}
},
),
],
),
);
return new Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: new BspServiceScreen(
bspServiceBloc: _bspServiceBloc,
bspSignupCommonModel: widget.bspSignupCommonModel,
servicesIds: servicesIds,
selection: selection,
searchQuery: _searchText,
selectedServices: selectedServices,
refresh: refresh,
),
);
}
}
Bsp_service_screen
class BspServiceScreen extends StatefulWidget {
final BspServiceBloc _bspServiceBloc;
final String searchQuery;
final List<int> servicesIds;
final Map<String, bool> selection;
final BspSignupCommonModel bspSignupCommonModel;
final List<BspServices.Service> selectedServices;
final Function refresh;
const BspServiceScreen({
Key key,
#required BspServiceBloc bspServiceBloc,
#required this.bspSignupCommonModel,
#required this.servicesIds,
#required this.selection,
#required this.selectedServices,
#required this.refresh,
this.searchQuery,
}) : _bspServiceBloc = bspServiceBloc,
super(key: key);
#override
BspServiceScreenState createState() {
return new BspServiceScreenState(_bspServiceBloc);
}
}
class BspServiceScreenState extends State<BspServiceScreen> {
final BspServiceBloc _bspServiceBloc;
BspServiceScreenState(this._bspServiceBloc);
// Map<String, bool> _selection = {};
#override
void initState() {
super.initState();
bool isHome = widget.bspSignupCommonModel.isHome;
bool isWalkIn = widget.bspSignupCommonModel.isWalkin;
bool isOnDemand = widget.bspSignupCommonModel.isOnDemand;
this._bspServiceBloc.dispatch(LoadBspServiceEvent(
countryId: 1,
isHome: isHome,
isOnDemand: isOnDemand,
isWalkin: isWalkIn,
));
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<BspServiceBloc, BspServiceState>(
bloc: widget._bspServiceBloc,
builder: (
BuildContext context,
BspServiceState currentState,
) {
if (currentState is UnBspServiceState) {
return Center(child: CircularProgressIndicator());
}
if (currentState is ErrorBspServiceState) {
return new Container(
child: new Center(
child: new Text(currentState.errorMessage ?? 'Error'),
),
);
}
if (currentState is InBspServiceState) {
// print(
// 'in bsp service state, ${currentState.bspServices.servicesByCountry.length}');
if (currentState.bspServices.servicesByCountry.length == 0) {
return Container(
child: Center(
child: Text("No Services available for this combination"),
),
);
} else {
return new Container(
child:
_renderServices(currentState.bspServices.servicesByCountry),
);
}
}
return Container();
},
);
}
List<ServicesByCountry> finalList = new List();
ListView _renderServices(List<ServicesByCountry> lovCountryServices) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.searchQuery != '') {
finalList.clear();
lovCountryServices.forEach((ServicesByCountry data) {
if (data.name
.toLowerCase()
.contains(widget.searchQuery.toLowerCase())) {
setState(() {
finalList.add(data);
});
} else {
data.services.forEach((ServiceList.Service services) {
if (services.name
.toLowerCase()
.contains(widget.searchQuery.toLowerCase())) {
setState(() {
finalList.add(data);
});
}
});
}
});
} else {
setState(() {
finalList.clear();
finalList.addAll(lovCountryServices);
});
}
});
return ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: finalList.length,
itemBuilder: (BuildContext context, int index) {
ServicesByCountry item = finalList[index];
List itemsList = item.services;
return ExpansionTile(
title: Text(item.name),
children: List.generate(itemsList.length, (i) {
widget.selection[itemsList[i].name] =
widget.selection[itemsList[i].name] ?? itemsList[i].isSelected;
return CheckboxListTile(
title: Text(itemsList[i].name),
value: widget.selection[itemsList[i].name],
onChanged: (val) {
setState(() {
widget.selection[itemsList[i].name] = val;
if (val) {
widget.servicesIds.add(itemsList[i].id);
List<BspServices.Service> services =
widget.selectedServices.where((service) {
return service.mainCategory == item.name;
}).toList();
SubCategory subService = new SubCategory(
id: itemsList[i].id,
name: itemsList[i].name,
);
List<SubCategory> subCategories = [];
if (services.length == 0) {
subCategories.add(subService);
widget.selectedServices.add(
new BspServices.Service(
mainCategory: item.name,
mainCategoryId: item.id,
subCategory: subCategories,
),
);
} else {
print('services in else');
print(services[0].subCategory);
subCategories = services[0].subCategory;
subCategories.add(subService);
}
} else {
widget.servicesIds.removeWhere((service) {
return service == itemsList[i].id;
});
List<BspServices.Service> services =
widget.selectedServices.where((service) {
return service.mainCategory == item.name;
}).toList();
services[0].subCategory.removeWhere((subService) {
return subService.id == itemsList[i].id;
});
}
});
print('widget.servicesIds after set state');
print(widget.servicesIds);
},
);
}),
);
},
);
}
}
You can use setState() after return to the first page:
Navigator.push(context, MaterialPageRoute(builder: (context) => Page2())).then((value) {
setState(() {
// refresh state
});
});
Please try below code:-
First you add one method async method:-
void redirectToNextScreen() async {
final Route route = MaterialPageRoute(
builder: (context) => BspServicePage(bspSignupCommonModel: model));
final result = await Navigator.push(mContext, route);
try {
if (result != null) {
if (result) {
//Return callback here.
}
}
} catch (e) {
print(e.toString());
}
}
Then Next you can call this method in "BSP_Signup_Terms_Page" on Next button Pressed event.
Second you can add below line in "BspServicePage" screen Next and Cancel Events.
Navigator.pop(mContext, true); //true means refresh back page and false means not refresh.

Flutter testWidgets with flutter_bloc - tests fail only when executed together

I'm having a problem with the attached widget tests in flutter. When I run the tests individually, each of them succeeds; however, when I run the entire main() method, the first three tests succeed but the last two fail with the following exception:
Expected: exactly one matching node in the widget tree
Actual: ?:<zero widgets with type "SuccessDialog" (ignoring offstage widgets)>
I understand that the exception means that the widget I'm expecting is not present - what I don't understand is why the test succeeds when run individually but fails after being run after other tests. Is there some instance I need to be "resetting" after each test?
I've tried inserting "final SemanticsHandle handle = tester.ensureSemantics();" at the start of each tests and "handle.dispose();" at the end of each test but got the same results.
EDIT:
After some further investigating it seems like the problem may be with how I manage bloc instances using the flutter_bloc package. I have altered my tests to create a new testWidget instance for each test but am still encountering the same problem. Is there anything I may be missing that would cause a bloc instance to persist across testWidget objects?
My new test code looks like this:
main() {
MvnoMockClient.init();
testWidgets(
'Voucher Redemption: Tapping redeem when no values were entered yields 2 field errors',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
expect(find.text("Field is required"), findsNWidgets(2));
});
testWidgets(
'Voucher Redemption: Tapping redeem when only voucher number was entered yields one field error',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.enterText(find.byType(PlainTextField), "0000000000");
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
expect(find.text("Field is required"), findsOneWidget);
});
testWidgets(
'Voucher Redemption: Tapping redeem when only mobile number was entered yields one field error',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.enterText(find.byType(MsisdnField), "0815029249");
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
expect(find.text("Field is required"), findsOneWidget);
});
testWidgets(
'Voucher Redemption: A successful server response yields a success dialog',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.enterText(find.byType(PlainTextField), "0000000000");
await tester.enterText(find.byType(MsisdnField), "0815029249");
await tester.tap(find.text("REDEEM"));
await tester.pump();
expect(find.byType(SuccessDialog), findsOneWidget);
});
testWidgets(
'Voucher Redemption: An unsuccessful server response yields an error dialog',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
gToken = "invalid";
await tester.enterText(find.byType(PlainTextField), "0000000000");
await tester.enterText(find.byType(MsisdnField), "0815029249");
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
gToken = "validToken";
expect(find.byType(ErrorDialog), findsOneWidget);
});
}
For additional reference, I have also included the code for the VoucherRedemptionPage and VoucherRedemptionScreen below:
class VoucherRedemptionPage extends StatelessWidget {
final onSuccess;
final onFail;
const VoucherRedemptionPage({Key key, #required this.onSuccess, #required this.onFail})
: super(key: key);
#override
Widget build(BuildContext context) {
var _voucherRedemptionBloc = new VoucherRedemptionBloc();
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/" + gFlavor + "/primary_background.png"),
fit: BoxFit.cover),
),
child: new Scaffold(
backgroundColor: Colors.transparent,
appBar: new AppBar(
title: new Text(gDictionary.find("Redeem Voucher")),
),
body: new VoucherRedemptionScreen(
voucherRedemptionBloc: _voucherRedemptionBloc,
onSuccess: this.onSuccess,
onFail: this.onFail,
),
),
);
}
}
class VoucherRedemptionScreen extends StatefulWidget {
const VoucherRedemptionScreen({
Key key,
#required VoucherRedemptionBloc voucherRedemptionBloc,
#required this.onSuccess,
#required this.onFail,
}) : _voucherRedemptionBloc = voucherRedemptionBloc,
super(key: key);
final VoucherRedemptionBloc _voucherRedemptionBloc;
final onSuccess;
final onFail;
#override
VoucherRedemptionScreenState createState() {
return new VoucherRedemptionScreenState(
_voucherRedemptionBloc, onSuccess, onFail);
}
}
class VoucherRedemptionScreenState extends State<VoucherRedemptionScreen> {
final VoucherRedemptionBloc _voucherRedemptionBloc;
final onSuccess;
final onFail;
TextEditingController _msisdnController = TextEditingController();
TextEditingController _voucherPinController = TextEditingController();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
VoucherRedemptionScreenState(
this._voucherRedemptionBloc, this.onSuccess, this.onFail);
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<VoucherRedemptionEvent, VoucherRedemptionState>(
bloc: _voucherRedemptionBloc,
builder: (
BuildContext context,
VoucherRedemptionState currentState,
) {
if (currentState is VoucherRedemptionInitial) {
_voucherPinController.text = currentState.scannedNumber;
return _buildFormCard();
}
if (currentState is VoucherRedemptionLoading) {
return Center(
child: CircularProgressIndicator(),
);
}
if (currentState is VoucherRedemptionSuccess) {
return SuccessDialog(
title: gDictionary.find("Voucher Redeemed Successfully"),
description: currentState.successMessage,
closeText: gDictionary.find("OK"),
closeAction: () {
this.onSuccess();
_voucherRedemptionBloc.dispatch(ResetVoucherRedemptionState());
},
);
}
if (currentState is VoucherRedemptionError) {
return ErrorDialog(
errorCode: currentState.errorCode,
errorMessage: currentState.errorMessage,
closeText: gDictionary.find("OK"),
closeAction: () {
this.onFail();
_voucherRedemptionBloc.dispatch(ResetVoucherRedemptionState());
},
);
}
},
);
}
Widget _buildFormCard() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8), topRight: Radius.circular(8))),
padding: EdgeInsets.fromLTRB(12, 12, 12, 0),
width: double.infinity,
height: double.infinity,
child: _buildCardContent(),
);
}
Widget _buildCardContent() {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
gDictionary.find("Transaction Amount"),
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryColorDark,
fontWeight: FontWeight.bold),
),
Container(height: 16),
Form(
key: _formKey,
child: _buildFormContent(),
),
],
),
);
}
Column _buildFormContent() {
return Column(
children: <Widget>[
PlainTextField(
controller: _voucherPinController,
label: gDictionary.find("Voucher Number"),
required: true,
),
Container(height: 16),
MsisdnField(
controller: _msisdnController,
label: gDictionary.find("Mobile Number"),
required: true,
),
Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
SecondaryCardButton(
text: gDictionary.find("SCAN VOUCHER"),
onPressed: () {
_voucherRedemptionBloc.dispatch(
ScanBarcode(),
);
},
),
Container(
width: 8.0,
),
PrimaryCardButton(
text: gDictionary.find("REDEEM"),
onPressed: () {
if (_formKey.currentState.validate()) {
_voucherRedemptionBloc.dispatch(
RedeemVoucher(
_voucherPinController.text,
_msisdnController.text,
),
);
}
},
),
],
)
],
);
}
}
Found the problem. I was using the singleton pattern when creating an instance of the bloc - this caused states to persist across different widget objects. Very unlikely that anyone will encounter the same problem that I did but below is the code that I changed to mitigate the problem
Old problematic code:
class VoucherRedemptionBloc
extends Bloc<VoucherRedemptionEvent, VoucherRedemptionState> {
static final VoucherRedemptionBloc _voucherRedemptionBlocSingleton =
new VoucherRedemptionBloc._internal();
factory VoucherRedemptionBloc() {
return _voucherRedemptionBlocSingleton;
}
VoucherRedemptionBloc._internal();
//...
}
Updated working code:
class VoucherRedemptionBloc
extends Bloc<VoucherRedemptionEvent, VoucherRedemptionState> {
VoucherRedemptionBloc();
//...
}
That likely happens because your tests mutate some global variable but do not reset their value.
One way to make it safe is to always use setUp and tearDown instead of mutating variables directly the main scope:
int global = 0;
void main() {
final initialGlobalValue = global;
setUp(() {
global = 42;
});
tearDown(() {
global = initialGlobalValue;
});
test('do something with "global"', () {
expect(++global, 43);
});
test('do something with "global"', () {
// would fail without setUp/tearDown
expect(++global, 43);
});
}
Similarly, if a test needs to modify a variable, use addTearDown instead of manually resetting the value later in the test.
DON'T:
int global = 0;
test("don't", () {
global = 43;
expect(global, 43);
global = 0;
})
DO:
int global = 0;
test('do', () {
global = 43;
addTearDown(() => global = 0);
expect(global, 43);
});
This ensures that the value will always be reset even if the tests fails – so that other test functions normally.
In my case I wasn't setting skipOffstage: false, doing something like this worked for me:
expect(find.text('text', skipOffstage: false), findsNWidgets(2));