Reputation: 1663
First, I need to say that I'm not sure if stackoverflow is the right place to ask this, but I have no one to ask except the community because we are working in small startup where there are no flutter developers besides two of us.
Me (about one year in development, and about a half of the year studying flutter) and one kinda "senior mobile developer with 10 years of experience" have fierce discussion.
The topic of the discussion is using non-final field in Stateless widget. He's doing it, he's writing such code. And he's saying that it is the best way to solve his problems. I'm saying that it is the bad idea and either he needs stateful widget or his design is bad and he doesn't need non-final field.
So my question is: Is there a situation in which the use of non-final field in stateless widget is justified?
His arguments:
I know that the first two arguments are silly and only 4th argument is worth to discuss.
Possible duplicate of this question also doesn't convince my colleague. Flutter: Mutable fields in stateless widgets
Please have a look at his code:
class GameDiscussThePicture extends StatelessWidget {
GameDiscussThePicture();
CarouselSlider _slider;
@override
Widget build(BuildContext context) {
return BlocBuilder(
bloc: BlocProvider.of<ChatBloc>(context),
condition: (previousState, state) {
return previousState != GameClosed();
},
builder: (context, state) {
if (state is GameDiscussTopicChanged) {
_showPictureWith(context, state.themeIndex);
} else if (state is GameClosed) {
Navigator.of(context).pop();
return Container();
}
final _chatBloc = BlocProvider.of<ChatBloc>(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Color.fromARGB(255, 255, 255, 255),
leading: BackButton(
color: Color.fromARGB(255, 12, 12, 13),
onPressed: () => BlocProvider.of<ChatBloc>(context).add(GameCancel()),
),
),
//SafeArea
body: DecoratedBox(
decoration: BoxDecoration(color: Color.fromARGB(255, 240, 240, 240)),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 15),
_carouselSlider(context),
Container(
height: 88,
child: DecoratedBox(
decoration: BoxDecoration(color: Color.fromARGB(255, 255, 255, 255)),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (_chatBloc.partnerAvatar() != null) Image.network(_chatBloc.partnerAvatar(), fit: BoxFit.cover, width: 75.0),
if (_chatBloc.partnerAvatar() == null) Text('RU', style: TextStyle(fontSize: 22)),
Padding(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_chatBloc.partnerName(), style: TextStyle(fontSize: 20, fontWeight: FontWeight.normal),),
ChatStopwatch(),
// Text('До конца 06:33', style: TextStyle(fontSize: 14, fontWeight: FontWeight.normal),),
],
)
),
// FlatButton(
// child: Image.asset('assets/images/mic_off.png', width: 30, height: 30,),
// onPressed: () => print('mic off pressed'),
// ),
FlatButton(
child: Image.asset('assets/images/hang_off.png', width: 60, height: 60,),
onPressed: () => ChatHelper.confirmEndingDialog(context)
),
]),
))
],
),
),
],
),
),
);
});
}
@widget
Widget _carouselSlider(BuildContext context) {
final chatBloc = BlocProvider.of<ChatBloc>(context);
_slider = CarouselSlider(
height: 600.0,
viewportFraction: 0.9,
reverse: false,
enableInfiniteScroll: false,
initialPage: chatBloc.gameDiscussCurrentIdx,
onPageChanged: (index) {
final chatBloc = BlocProvider.of<ChatBloc>(context);
if (chatBloc.gameDiscussCurrentIdx < index) {
chatBloc.add(GameDiscussTopicChange(themeIndex: index));
} else {
_slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
}
},
items: chatBloc.gameDiscussPictures.map((item) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 5.0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.titleEn, style: Styles.h3),
SizedBox(height: 15.0,),
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
child: Image.network(item.getImageUrl(), fit: BoxFit.cover, width: MediaQuery.of(context).size.width),
)
]
),
);
},
);
}).toList(),
);
return _slider;
}
_onPictureChanged(BuildContext context, int index) {
final chatBloc = BlocProvider.of<ChatBloc>(context);
if (chatBloc.gameDiscussCurrentIdx < index) {
chatBloc.add(GameDiscussTopicChange(themeIndex: index));
} else {
_slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
}
}
_showPictureWith(BuildContext context, int index) {
final chatBloc = BlocProvider.of<ChatBloc>(context);
chatBloc.gameDiscussCurrentIdx = index;
_slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
}
}
Upvotes: 1
Views: 2540
Reputation: 2864
Disclaimer: I am not good at explaining, hope you get something reading this trash explanation. I don't even think this can be called explanation
class GameDiscussThePicture extends StatelessWidget {
GameDiscussThePicture();
/// As he said, BLoC is the one holding state, therefore if he wants a non final field
/// declare it in ChatBloc not here.
/// class ChatBloc extends Bloc {
/// CarouselSlider _slider;
/// CarouselSlider get slider => _slider;
/// }
/// To access it, BlocProvider.of<ChatBloc>(context).slider;
CarouselSlider _slider;
@override
Widget build(BuildContext context) {
return BlocBuilder(
bloc: BlocProvider.of<ChatBloc>(context),
condition: (previousState, state) {
return previousState != GameClosed();
},
builder: (context, state) {
if (state is GameDiscussTopicChanged) {
_showPictureWith(context, state.themeIndex);
} else if (state is GameClosed) {
Navigator.of(context).pop();
return Container();
}
final _chatBloc = BlocProvider.of<ChatBloc>(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Color.fromARGB(255, 255, 255, 255),
leading: BackButton(
color: Color.fromARGB(255, 12, 12, 13),
/// he can just write _chatBloc.add(GameCancel()) here.
onPressed: () => BlocProvider.of<ChatBloc>(context).add(GameCancel()),
),
),
//SafeArea
body: DecoratedBox(
decoration: BoxDecoration(color: Color.fromARGB(255, 240, 240, 240)),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 15),
_carouselSlider(context),
Container(
height: 88,
child: DecoratedBox(
decoration: BoxDecoration(color: Color.fromARGB(255, 255, 255, 255)),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (_chatBloc.partnerAvatar() != null) Image.network(_chatBloc.partnerAvatar(), fit: BoxFit.cover, width: 75.0),
if (_chatBloc.partnerAvatar() == null) Text('RU', style: TextStyle(fontSize: 22)),
Padding(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_chatBloc.partnerName(), style: TextStyle(fontSize: 20, fontWeight: FontWeight.normal),),
ChatStopwatch(),
// Text('До конца 06:33', style: TextStyle(fontSize: 14, fontWeight: FontWeight.normal),),
],
)
),
// FlatButton(
// child: Image.asset('assets/images/mic_off.png', width: 30, height: 30,),
// onPressed: () => print('mic off pressed'),
// ),
FlatButton(
child: Image.asset('assets/images/hang_off.png', width: 60, height: 60,),
onPressed: () => ChatHelper.confirmEndingDialog(context)
),
]),
))
],
),
),
],
),
),
);
});
}
@widget
/// Also rather than passing BuildContext, passing the _chatBloc is better.
/// I am not sure why, but I've read somewhere BuildContext is not meant to be passed
/// around. And you don't need to make another final field for BlocProvider.of<ChatBloc>
/// (context)
/// Widget _carouselSlider(ChatBloc chatBloc) {
/// and here you can do something like chatBloc.slider = CarouselSlider(); in case
/// that slider field will be used again somehow.
/// }
/// Tho just return CarouselSlider instead is better in this scenario IMO.
Widget _carouselSlider(BuildContext context) {
final chatBloc = BlocProvider.of<ChatBloc>(context);
_slider = CarouselSlider(
height: 600.0,
viewportFraction: 0.9,
reverse: false,
enableInfiniteScroll: false,
initialPage: chatBloc.gameDiscussCurrentIdx,
onPageChanged: (index) {
final chatBloc = BlocProvider.of<ChatBloc>(context);
if (chatBloc.gameDiscussCurrentIdx < index) {
chatBloc.add(GameDiscussTopicChange(themeIndex: index));
} else {
_slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
}
},
items: chatBloc.gameDiscussPictures.map((item) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 5.0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.titleEn, style: Styles.h3),
SizedBox(height: 15.0,),
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
child: Image.network(item.getImageUrl(), fit: BoxFit.cover, width: MediaQuery.of(context).size.width),
)
]
),
);
},
);
}).toList(),
);
return _slider;
}
_onPictureChanged(BuildContext context, int index) {
final chatBloc = BlocProvider.of<ChatBloc>(context);
if (chatBloc.gameDiscussCurrentIdx < index) {
chatBloc.add(GameDiscussTopicChange(themeIndex: index));
} else {
_slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
}
}
_showPictureWith(BuildContext context, int index) {
final chatBloc = BlocProvider.of<ChatBloc>(context);
chatBloc.gameDiscussCurrentIdx = index;
_slider.animateToPage(chatBloc.gameDiscussCurrentIdx, duration: Duration(milliseconds: 300), curve: Curves.easeInOut);
}
}
Upvotes: 1