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),
),
],
),
);
},
)
)
],
),
),
),
),
);
}
}
Related
I'm trying to make a drawer widget that uses a ListViewBuilder to populate itself based on a list injected into the ViewModel.
However, I'm having issues getting it to play ball.
I've wrapped the LVB in a SizedBox to provide it with vertical bounds (since it was throwing a bunch of errors, as suggeested by another answer, and that's stopped those, but now I'm getting an overflow.
The header also doesn't fill out the width anymore either.
class MainDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<MainDrawerViewModel>(builder: (context, model, child) {
return Drawer(
child: Column(
children: [
DrawerHeader(
decoration: const BoxDecoration(color: ThemeColors.primaryDark),
child: Text(S.current.drawerTitle, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 30)),
),
SizedBox(
height: double.maxFinite,
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: model.mainDrawerItems.length,
itemBuilder: (_, index) {
final drawerItem = model.mainDrawerItems[index];
return ListTile(
leading: drawerItem.icon,
title: Text(drawerItem.title, style: Theme.of(context).textTheme.headline6),
selected: model.currentScreen == drawerItem.screen,
selectedTileColor: ThemeColors.selectedDrawerItem,
onTap: () {
model.selectScreen(drawerItem.screen);
Navigator.pop(context);
},
);
}),
),
],
));
});
}
}
This feels like something that should be pretty easy... What am I missing here?
Use Expanded widget on listView instead of height: double.maxFinite,
Expanded(
child: ListView.builder(
padding: EdgeInsets.zero,
double.maxFinite = 1.7976931348623157e+308; and it is equal to 1.7976931348623157 × 10^308 which is too big. and the overflow happens.
For header, you can wrap With SizedBox and provide width: double.maxFinite,. Also you can just use a container with decoration like
class MainDrawer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: [
Container(
width: double.maxFinite,
height: 200, // based on your need
decoration: const BoxDecoration(color: Colors.amber),
padding: EdgeInsets.only(left: 16, top: 16),
child: Text(
"S.cu ",
style: TextStyle(
color: ui.Color.fromARGB(255, 203, 19, 19),
fontWeight: FontWeight.bold,
fontSize: 30),
),
),
Expanded(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: 3,
itemBuilder: (_, index) {
return ListTile(
leading: Icon(Icons.abc_outlined),
title: Text("drawerItem.title",
style: Theme.of(context).textTheme.headline6),
selected: true,
onTap: () {},
);
}),
),
],
));
}
}
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 tasked with the challenge of getting a black banner to show when a user returns from a screen on that same tab upon ordering an item on another screen. The current code below only shows the black banner appropriately if the user leaves by selecting another tab then returns to the screen. The part that I need to be dynamically presented starts with "if (OrderProvider.listOrderSummary.isNotEmpty)". I believe that this involves making a stateless widget stateful, but that results in errors that suggest that may not be the right approach.
class GridItems extends StatelessWidget {
GridItems({#required this.infinityPageController});
final InfinityPageController infinityPageController;
#override
Widget build(BuildContext context) {
final List<Item> itemList = Provider.of<List<Item>>(context);
if (itemList == null) {
return Center(
child: CupertinoActivityIndicator(
radius: 20,
),
);
}
final List<Item> mapItemList = itemList.toList();
return Column(
children: <Widget>[
TopMenuNavigation(
infinityPageController: infinityPageController,
title: 'ALL ITEMS',
),
Divider(
color: Colors.grey,
),
Expanded(
child: GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: itemList.length,
itemBuilder: (BuildContext contex, int index) {
return Center(
child: InkWell(
onTap: () {
Navigator.of(context)
.pushNamed('/order', arguments: mapItemList[index]);
},
child: Container(
width: 310,
margin: EdgeInsets.all(7),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: 1, color: Colors.grey),
borderRadius: BorderRadius.all(
Radius.circular(7.0),
),
),
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10, top: 10),
width: double.infinity,
child: Text('${mapItemList[index].itemName}'),
),
Expanded(
child: Image.network(
'${mapItemList[index].imageUrl}',
),
),
],
),
),
),
);
},
),
),
if (OrderProvider.listOrderSummary.isNotEmpty)
GestureDetector(
onTap: () {
Navigator.of(context).pushNamed('/review_order');
},
child: Container(
height: 55,
color: Colors.black,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Spacer(),
Text(
'REVIEW ORDER (${OrderProvider.listOrderSummary.length} items)',
style: TextStyle(
fontSize: 17,
color: Colors.white,
fontFamily: 'OpenSans',
fontWeight: FontWeight.bold),
),
SizedBox(width: 8),
Icon(
Icons.navigate_next,
color: Colors.white,
size: 35,
),
SizedBox(
width: MediaQuery.of(context).size.height * 0.065,
),
],
),
),
)
else
SizedBox(),
],
);
}
}
You have to somehow rebuild your screen (or part of it) where you want to return to. This is where a state-management solution should come into place :).
Create a BLoC or a ValueNotifier and rebuild the widget with a StreamBuilder or a ValueListenableBuilder. If you want to make it simple just create a shared state inside a stateful widget between your tab screens and call setState if the black banner should appear
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!
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().