The build() function is triggered when TextField comes to focus on tap. If you run the following app, notice the TextField at bottom. If TextField is tapped, it causes build() to run and entire screen is rebuilt. Anyway to prevent build() from running when this field comes to focus?
Generally speaking, What causes the build function to run and how to restructure a program that prevents the entire screen from repainting even if the build is triggered?
class TestPad extends StatefulWidget {
_TestPadSate createState() => new _TestPadSate();
final Bloc _bloc = new Bloc();
}
class _TestPadSate extends State<TestPad>{
static final txtAreaController = TextEditingController();
#override
void initState() {
super.initState();
widget._bloc.fetchVal();
}
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
appBar: new AppBar(),
body: StreamBuilder(
stream: widget._bloc.getVal,
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.waiting){
return buildProgressIndicator(true, width: 50, height: 50);
}
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: new Container(
margin: EdgeInsets.only(
),
//height: MediaQuery.of(context).size.height - 210,
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Colors.grey))),
child: ListView(
reverse: true,
shrinkWrap: true,
children: List(),
),
)
),
Container(
margin: EdgeInsets.only(
top: 2.0,
),
decoration: new BoxDecoration(
color: Colors.white,
),
height: 72,
child: new Column(
children: <Widget>[
textAreaSection(context),
],
)),
],
);
}
)
)
);
}
Widget _postBtn() {
return IconButton(
icon: Icon(Icons.send, size: 22,),
);
}
Widget textAreaSection(context) {
return Row(
mainAxisSize: MainAxisSize.min,
//mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: textFormFieldScroller(),
),
_postBtn()
],
);
}
Widget textFormFieldScroller() {
return SingleChildScrollView(
physics: ClampingScrollPhysics(),
child: Form(
child: TextFormField(
controller: txtAreaController,
keyboardType: TextInputType.text,
style: new TextStyle(
//height: 0.5,
color: Colors.grey),
autofocus: false,
//initialValue: "What's on your mind?",
// maxLines: null,
decoration: new InputDecoration(
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
hintText: "What's on your mind?",
hintStyle: new TextStyle(
color: Colors.grey[600],
),
fillColor: Colors.white,
filled: true,
border: InputBorder.none,
),
)
),
);
}
Widget buildProgressIndicator(showIndicator,{double width, double height}) {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: showIndicator ? 1.0 : 0.0,
child: Container(
width: width??10.0,
height: height??10.0,
child: new CircularProgressIndicator(
)),
),
),
);
}
}
class Bloc {
final _fetcher = BehaviorSubject<String>();
Observable<String> get getVal => _fetcher.stream;
fetchVal() async{
await Future.delayed(Duration(seconds: 2), (){
_fetcher.sink.add("test");
});
}
dispose() {
_fetcher.close();
}
}
setState() causes a rebuild. I believe that the Flutter team don't consider rebuilding the screen a problem because it is very fast. However, the thing to do is to separate the collection of data (eg. Select) from the rebuild. IE. Structure your program so that if a re-select of data is done, then it is separate from the build. If and when a re-Select of data is required and the screen needs to be repainted then do a setState().
Related
I'm trying to create a listview of cards whose images get displayed in the listview only if the card is selected. The selection widget is the PopupSubscription() where I'm choosing which cards (SubscriptionCard) to display by setting the bool of that particular image to be true. But when the selections are applied, the selected images don't appear immediately even after doing setState().
However, they do appear when I switch tabs and return the screen again. What should I do in order to change the state of an object that's not in my current state class? I tried using Provider but it left me confused as to what I'm supposed to do.
This is the SubscriptionCard where the bool is set on tapping it:
return InkWell(
radius: 1.0,
borderRadius: BorderRadius.circular(8.0),
highlightColor: buttonBackground,
onTap: () {
setState(() {
widget.currentSubscription.selected = !widget.currentSubscription.selected;
});
},
child: Card(
elevation: 1.0,
borderOnForeground: true,
shape: widget.currentSubscription.selected ? RoundedRectangleBorder(
borderRadius: BorderRadius.circular(3.0),
side: BorderSide(color: buttonBackground, width: 2.0),
) : ContinuousRectangleBorder(),
color: bgDarkColor,
child: SizedBox(
width: SizeConfig.blockSizeHorizontal * 30,
child: Stack(
alignment: Alignment.topRight,
children: [
Row(
mainAxisSize: MainAxisSize.max,
children: [
Image.asset(this.widget.currentSubscription.logo, height: 35.0,),
Text(
' ${this.widget.currentSubscription.name}',
style: TextStyle(fontFamily: 'Muli', fontSize: 16.0)
),
],
),
widget.currentSubscription.selected ? Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: buttonBackground,
),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Icon(
Icons.check,
size: 10.0,
color: Colors.white,
),
),
) : Container()
],
),
),
),
);
This is the ListView where the selected cards' images are rendered:
Container(
height: 50,
width: 350,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return PopupSubscription();
}
);
},
icon: Icon(Icons.add_box_rounded, size: 30.0,),
),
StatefulBuilder(
builder: (context, setState) {
return ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: subscriptionsList.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 5.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(25.0),
child: Image.asset(
subscriptionsList[index].selected ? subscriptionsList[index].logo
: "assets/elements/streaming-services/netflix.jpeg",
),
),
);
},
);
}
),
],
)
),
Based on your current code, I'm guessing you've added the currentSubscription variable as final in the StatefulWidget above the actual State, like:
class MyClass extends StatefulWidget{
final currentSubscription;
// Rest of the code
}
class _MyClassState extends State<MyClass> {
// Your ListView Code and other stuff.
}
This wont work when you want to change the state onTap, I recommend making the variable you use in setState within the _MyClassState class and use setState in that. Something like:
class _MyClassState extends State<MyClass> {
bool _isSelected = false;
// And in your onTap method, something like:
setState(() {
_isSelected = !_isSelected;
});
}
I am using the animation package to create a popup modal However building the widget inside the model is very noticeably slow and is making making the popup take time to open. I am trying to put a loading indicator when opening the modal then build the widget afterward and just update.
I am lost on how to accomplish that.. it would be highly appreciated if someone could help.
this is the animation package method for the modal
ElevatedButton(
onPressed: () {
showModal<void>(
context: context,
builder: (BuildContext context) {
// building _ExampleAlertDialog takes time
return _ExampleAlertDialog(loading: loading);
},
).then((state) => setState(() => {loading = !loading}));
},
child: const Text('SHOW MODAL'),
),
the _ExampleAlertDialog is supposed to be a listView
class _ExampleAlertDialog extends StatefulWidget {
_ExampleAlertDialog({
this.loading,
});
final bool loading;
#override
__ExampleAlertDialogState createState() => __ExampleAlertDialogState();
}
class __ExampleAlertDialogState extends State<_ExampleAlertDialog> {
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 50, horizontal: 20),
child: Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(5),
child: Material(
child: Column(
children: [
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor))),
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [Icon(Icons.arrow_back), Icon(Icons.refresh)],
),
),
// This is the Listview I am trying to avoid onLoad
Expanded(child: widget.loading == true ? Container():
ListView.builder(
itemCount: 16,
itemBuilder: (BuildContext ctxt, int index) {
return Container(
// width: MediaQuery.of(context).size.width * 1,
child: Row(
children: <Widget>[
Icon(
Icons.radio_button_unchecked,
color: Colors.blue,
size: 12.0,
),
SizedBox(
width: 20.0,
),
Text(
"Travetine",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w400),
),
],
),
);
},
)
)
],
),
),
),
),
);
}
}
I'm making a Flutter Web App and I have something like this
class _HomeViewState extends State<HomeView> {
#override
Widget build(BuildContext context) {
var posts = Provider.of<List<Post>>(context);
List<int> l =[1,2,3,4];
return Scaffold(
backgroundColor: Colors.white,
appBar: TheAppBaar,
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Container(child: Padding(
padding: EdgeInsets.all(20.0),
child: Column(
children: [
CreatePost(connUserUid: widget.user.uid, connUserImage:
userFetched.get('profileImage').toString(),
connUserFullname: userFetched.get('fullName').toString(),),
SizedBox(height: 10,),
ListView.separated(itemBuilder: (BuildContext context, int
index) {
return Container(
height: 150,
child: Text('${l[index]}'),
);
},
separatorBuilder:
(BuildContext context, int index) => Divider(),
itemCount: l.length)
],
),
),
),
),
);
}
}
Every time I want to display a ListView with those posts fetched or anything, the method Create Post and that Sized Box disappear and the list is not displayed either.
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: Container(
width: 400,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(10)),
boxShadow: [BoxShadow(
color:kPrimaryColor.withOpacity(0.2),
spreadRadius: 2,
blurRadius: 3,
offset: Offset(0,3)
)]
),
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.center,
child: Container(
height: 50,
child: Image.network(widget.connUserImage),
),
),
Align(
alignment: Alignment.center,
child: Container(width: 30,child: Divider(
color: kPrimaryColor,
thickness: 2,
),),
),
SizedBox(height: 8,),
TextField(
maxLines: 5,
decoration: InputDecoration(
errorText: errorTextMessage,
border: OutlineInputBorder(
borderSide: BorderSide(color: kPrimaryColor)
),
labelText: 'Yadda, Yadda, Yadda...'
),
onChanged: (String value) {
setState(() {
postDescription = value;
errorTextMessage = null;
spinner = false;
});
},
),
SizedBox(height: 12,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
IconButton(
icon: Icon(Icons.add_a_photo_rounded, color: Color
(0xFFE53E00),
size:
25,),
onPressed: () {
imagePicker();
},
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: imageInfo == null ? Container() : Text(imageInfo.fileName),
)
],
),
Icon(Icons.pin_drop_rounded, color: Color(0xFF0077e5),
size:
25,),
Icon(Icons.map_rounded, color: Color(0xFF26c118),
size: 25,),
spinner? Padding(
padding: EdgeInsets.all(15.0),
child: CircularProgressIndicator(backgroundColor: kPrimaryColor,),
) :
IconButton(icon:
Icon(Icons
.arrow_forward_ios_outlined,
color:
kPrimaryColor,size: 25,), onPressed: () {
},),
],
)
],
),
),
),
);
}
This is the CreatePost method, just a " card ",
If I make a ListView all disappear, but if in my Column from the first code, I add 1 PostView by 1, they are displayed. I don't understand what's wrong. Should I remove those Align or I don't know. In my console I do not have any errors.
Thank you in advance!
What I want to do is make a TextField stay in the same position by scrolling down the screen. I want to know if there is a way to do this?
This is the TextField that I want to be floating:
This is the code, the CardWidget are just cards and searchInput is the textField:
class RouteListPage extends StatefulWidget {
#override
_RouteListPageState createState() => _RouteListPageState();
}
class _RouteListPageState extends State<RouteListPage> {
TextEditingController searchController = new TextEditingController();
#override
Widget build(BuildContext context) {
final _screenSize = MediaQuery.of(context).size;
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
searchInput(),
CardWidget(),
CardWidget(),
CardWidget(),
CardWidget(),
SizedBox(height: 25.0)
],
),
),
);
}
Widget searchInput(){
return Container(
margin: EdgeInsets.symmetric(horizontal: 24, vertical: 25.0),
padding: EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
color: Color(0xfff6f6f6),
borderRadius: BorderRadius.circular(30),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black45,
offset: Offset(0.0, 2.0),
blurRadius: 10.0,
),
],
),
child: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: "Buscar rutas",
hintStyle: TextStyle(fontFamily: "WorkSansSemiBold", fontSize: 16.0),
border: InputBorder.none
),
)
),
InkWell(
onTap: () {},
child: Container(
child: Icon(Icons.search, color: Tema.Colors.loginGradientEnd, size: 28.0)
)
)
],
),
);
}
}
You can use a Stack widget and have the scrolling widget below the TextField widget. Use the Positioned widget to control the position of searchInput()
Your build method will change to this:
#override
Widget build(BuildContext context) {
final _screenSize = MediaQuery.of(context).size;
return Scaffold(
body: Stack(
children: <Widget>[
SingleChildScrollView(
child: Column(
children: <Widget>[
CardWidget(),
CardWidget(),
CardWidget(),
CardWidget(),
SizedBox(height: 25.0)
],
),
),
Positioned(
top: 24.0,
left: 0.0,
right: 0.0,
child: searchInput(),
)
],
),
);
}
Instead of SingleChildScrollView try using this
Stack(
children: <Widget>[
searchInput(),
ListView(
children: <Widget>[
CardWidget(),
CardWidget(),
CardWidget()
],
)
],
),
Use a Stack and add your scrollview and the input as children:
Stack(
children: [
SingleChildScrollView(),
searchInput(),
],
)
I am trying to create a custom style checkbox that is a container with rounded edges. It should show a different color icon when tapped. I am not sure how to do this can anyone help? Here is the code: (Edit I updated the code to show the gridview builder that the checkbox is placed in. The gridviewbuilder builds a card based on the length of a list in the provider class. I am trying to get the checkbox to work independently of the other gridview cards.
//this is the function in the provider class
toggleCheckbox(bool checkboxStatus){
if (checkboxStatus = false){
return checkboxStatus = true;
} else if (checkboxStatus = true){
return checkboxStatus = false;
}
notifyListeners();
}
GridView.builder(
itemCount: bloc.readingList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 12/16, crossAxisCount: 2, crossAxisSpacing: 20.0, mainAxisSpacing: 20),
itemBuilder: (BuildContext context, int index) {
return ClipRRect(
borderRadius: BorderRadius.circular(16.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(3.0, 6.0),
blurRadius: 10.0)
],
borderRadius: BorderRadius.circular(20),
image: DecorationImage(
image: NetworkImage('${bloc.readingList[index].storyImage}'),
fit: BoxFit.cover,
),
),
child: Padding(
padding: const EdgeInsets.only(
top: 8.0, bottom: 0.0, left: 0.0, right: 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
TopicTag(index: index,),
CardContents(),
],
),
),
),
);
},
),
class CardContents extends StatefulWidget {
const CardContents({
Key key,
}) : super(key: key);
#override
_CardContentsState createState() => _CardContentsState();
}
class _CardContentsState extends State<CardContents> {
bool checkboxStatus = false;
#override
Widget build(BuildContext context) {
final bloc = Provider.of<ReadingListBloc>(context);
return ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Container(
color: Colors.blueGrey.withOpacity(.5),
child: Column(
children: <Widget>[
Text('Title Here (color change with topic)',
style: TextStyle(
color: Colors.white,
fontSize: 18.0,
fontFamily: "Calibre-Semibold",
letterSpacing: 1.0,
)),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 8.0, bottom: 8),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white),
child: GestureDetector(
onTap:(){},
// bloc.toggleCheckbox(checkboxStatus),
child: checkboxStatus
? Icon(Icons.check_circle, color: Colors.green)
: Icon(Icons.check_circle, color: Colors.white)),
),
),
],
),
],
),
),
),
);
}
}
Considering that you have setup your provider correctly, you could do something like below.
// add the property below to your provider
bool checkboxStatus = false;
void toggleCheckbox(){
checkboxStatus = !checkboxStatus;
notifyListeners();
}
Now for the listeners, you can just check this new property, considering that bloc is your provider.
GestureDetector(
onTap: () {
bloc.toggleCheckbox(checkboxStatus);
},
child: bloc.checkboxStatus ? Icon(Icons.check_box, color: Colors.green)
: Icon(Icons.check_circle, color: Colors.white)),
I'm assuming that for some reason you want to use a provider. Of course you could also use a StatefulWidget if you only need the status in this widget.