Flutter prevent widget rebuild - flutter

When i select TextField()(means keyboard open) my widget tree rebuilt.
I know the reason for this is because when the keyboard is opened, the screen sizes change and the whole class is rebuilt (both StateFull and Stateless widgets). And this is normal.
And so the AnimatedList() in my class is rebuilt. I do not want it to be rebuilt (because list indexes and list items in it change). How can I prevent this AnimatedList() Widget rebuilt? Or is it possible to prevent the class from being rebuilt when the keyboard is opened in general?
Please Help Me
Here my code:
class CommentPage extends StatelessWidget {
final String activityname;
final String postid;
final String usernameuid;
const CommentPage({
Key key,
#required this.activityname,
#required this.postid,
#required this.usernameuid,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Container(
height: 400,
child: Scaffold(
resizeToAvoidBottomInset: false,
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text('Coments'),
centerTitle: true,
),
body: Container(
height: 350,
width: 300,
child: ChangeNotifierProvider(
create: (context) => CommentLikeController(),
builder: (context, child) => FutureBuilder<List<Comment>>(
future:
Provider.of<CommentLikeController>(context, listen: false)
.initial(),
builder: (context, snapshot) {
if (snapshot.hasData)
return Stack(children: [
Positioned(
bottom: 50,
child: Container(
height: 350,
width: 300,
child: AnimatedList(
shrinkWrap: true,
reverse: true,
key: Provider.of<CommentLikeController>(context)
.listkey,
initialItemCount: snapshot.data.length,
itemBuilder: (context, index, animation) {
return ListItem(
index: index,
postid: postid,
activityname: activityname,
usernameuid: usernameuid,
);
},
),
),
),
Positioned(
bottom: 0,
left: 0,
child: Container(
height: 50,
width: 350,
child: TextField(),
),
),
]);
else
return LinearProgressIndicator();
}),
),
),
),
),
);
}
}

Create another StatefulWidget and place your TextField widget inside it. Create the text field controller in your CommentPage and pass it as parameter to your new created StatefulWidget and use it as the the TextField's controller. The keyboard effect will only impact the new created StatefulWidget

Related

Could not complete vertical scroll of Listview inside InteractiveViewer

I have a requirement where I need to scroll through a list of images and also zoom and pan them. Very similar to a pdf document viewer. So I Used a ListView to show the pages and added the ListView as child to InteractiveViewer.
After zooming in I could not scroll to the top or bottom end of the ListView.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: InteractiveViewer(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: 10,
itemBuilder: (context, _index) {
print(_index);
return Container(
color: Colors.grey,
height: MediaQuery.of(context).size.width * 1.1,
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(2),
child: Container(
padding: EdgeInsets.all(2),
color: Colors.white,
child: Center(
child: Text('Page index: $_index'),
),
),
);
},
),
scaleEnabled: true,
panEnabled: true,
),
);
}
I guess it might be due to the InteractiveViewer handling the scroll gesture of ListView.
Is there a way to avoid vertical gesture to be handled by InteractiveViewer?
I don't think there is a way to make the two element have their scroll behavior working together, as InteractiveViewer allows you to move after zooming in your image.
Would it fulfill your requirement to set the image to fullscreen when taping on the image to zoom ?
That way you keep the scroll handled by the ScrollView and separate the InteractiveViewer to another view.
Something like that, you wrap all of your images with the ImageDetails widget and remove your InteractiveViewer from the Scaffold:
class ImageDetails extends StatelessWidget {
final String url;
const ImageDetails({Key key, this.url}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Hero(
tag: 'tag$url',
child: NetworkImage(
imageUrl: url,
fit: BoxFit.cover,
),
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return FullScreenImage(
url: url,
);
}));
});
}
}
class FullScreenImage extends StatelessWidget {
final String url;
const FullScreenImage({Key key, this.url})
: super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
child: InteractiveViewer(
maxScale: 2.0,
minScale: 1.0,
child: Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Hero(
tag: 'tag$url',
child: SizedBox(
width: MediaQuery.of(context).size.width,
child:
NetworkImage(imageUrl: url, fit: BoxFit.fitWidth),
),
),
),
),
),
onTap: () {
Navigator.pop(context);
},
);
}
}

Flutter Web sliver: customscrollview

I'm making a flutter web.
I want to resize a horizontal image in a CustomScrollView.
Ex: On a product detail page, I want to place the top image size 600x400 width in the center of the layout.
Regardless of the image size, it is a problem that it expands to 900x or more horizontally.
layout:
code:
class ServiceProductDetailPage extends StatelessWidget {
final String productId;
final ServiceProductModel product;
ServiceProductDetailPage({required this.product, required this.productId});
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
expandedHeight: 50,
title: Text('Title'),
stretch: true,
backgroundColor: Colors.black,
),
SliverToBoxAdapter(child: _Body(context, _product)),
],
),
);
}
}
Widget _Body(BuildContext context, ServiceProductModel product) {
return Card(
child: ConstrainedBox(
constraints: BoxConstraints.tight(Size(900,400)
),
child: LoadingImage(url: product.image),
),
);
}
class LoadingImage extends StatelessWidget {
const LoadingImage({
Key? key,
required this.url,
}) : super(key: key);
final String url;
#override
Widget build(BuildContext context) {
return Image.network(
url,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
errorBuilder: (BuildContext c, Object err, StackTrace? stack) {
return const Center(child: Text('error'));},
frameBuilder: (BuildContext c, Widget image, int? frame, bool sync){
if (!sync && frame == null) {
return const Center(child: CircularProgressIndicator());
}
return image;
},
);
}
}
What you are looking for is an UnconstrainedBox. As per the documentation, the UnconstrainedBox makes itself take the full width of it's parent, but it allows it's child to be of any width the child decides.
So, you can use it like this.
Widget _Body(BuildContext context, ServiceProductModel product) {
return Card(
child: UnconstrainedBox(
child: Container(
color: Colors.deepOrange,
width: 900,
height: 400,
child: LoadingImage(url: product.image),
),
),
);
}
Next, you given some properties to you Image.network that is making it expand.
So remove the following
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
And just add this
fit: BoxFit.contain,
I have given your 900 width Container a color to see that it stays at 900 despite the window being larger than 900.

Flutter state rebuild when keyboard appears

Hello Everyone!
I am using Modal BottomSheet for view comments. When I click TextField (Comment Page) all widgets rebuilding also parents! Why all Flutter rebuilding all widgets. And why also parents widgets rebuilding? I know when keyboard appears or rotation changed eg. both StatefulWidget and StateLessWidget rebuilded. But I can’t do something in this situation. Please help me
Here CommentPage.
class CommentPage extends StatelessWidget {final String activityname;
final String postid;
final String usernameuid;
const CommentPage(
{Key key,
#required this.activityname,
#required this.postid,
#required this.usernameuid,
}): super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Container(
height: MediaQuery.of(context).size.height * 0.8,
child: Scaffold(
resizeToAvoidBottomInset: false,
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text('Coments'),
centerTitle: true,
),
body: Container(
height:MediaQuery.of(context).size.height * 0.8,
width: MediaQuery.of(context).size.width,
child: ChangeNotifierProvider(
create: (context) => CommentLikeController(),
builder: (context, child) => FutureBuilder<List<Comment>>(
future: Provider.of<CommentLikeController>(context,
listen: false)
.initial(),
builder: (context, snapshot) {
if (snapshot.hasData)
return Stack(children: [
Positioned(
bottom: 50,
child: Container(
height:
MediaQuery.of(context).size.height * 0.8 -
105,
width: MediaQuery.of(context).size.width,
child: AnimatedList(
shrinkWrap: true,
reverse: true,
key: Provider.of<CommentLikeController>(
context)
.listkey,
initialItemCount: snapshot.data.length,
itemBuilder: (context, index, animation) {
return ListItem(
index: index,
postid: postid,
activityname: activityname,
usernameuid: usernameuid,
);
},
),
),
),
Positioned(
bottom: 0,
right: 0,
child: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Provider.of<CommentLikeController>(
context,
listen: false)
.add();
})),
Positioned(
bottom: 0,
left: 0,
child: Container(
height: 50,
width:
MediaQuery.of(context).size.width - 50,
child: TextField()))
]);
else
return LinearProgressIndicator();
})),
)),
),
);
}
}
Parents CommentPage
class PageviewItem extends StatelessWidget {
final DBcontroller value;
final int index;
final String activityname;
final String username;
const PageviewItem(
{Key key,
#required this.value,
#required this.index,
#required this.activityname,
#required this.username})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: [
Container(
child: value.posts[index].urln.contains('.mp4')
? VideoItem(value: value, index: index)
: PhotoItem(value: value, index: index),
),
UserInfo(value: value, index: index),
Positioned(
bottom: 5,
left: 5,
child: GestureDetector(
onTap: () {
showpopup(context);
}, //show pop up
child: Container(
decoration: BoxDecoration(
color: Colors.blue[400].withOpacity(0.3),
borderRadius: BorderRadius.all(Radius.circular(5))),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: RichText(text: TextSpan(text: 'Comment')),
),
),
)),
Header(activityname: activityname),
],
),
);
}
showpopup(context) {
return showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10), topRight: Radius.circular(10))),
isScrollControlled: true,
context: context,
builder: (context1) {
return CommentPage(
activityname: activityname,
postid: value.posts[index].from_uid,
usernameuid: username,
);
},
);
}
}
Note I am also have PageviewItem parents cLass. And each one rebuilded when click TextField(keyboard appears )
I'm not good at explaining but maybe I can give an example
class _CommentPageState extends State<CommentPage> {
late Future<QuerySnapshoot> commentStream; //late initialization for future comment
#ovveride
void initState(){
commentStream = FirebaseFirestore.instance.collection('forumchat').snapshot //add this on initstate
} //first initial when commentpage loaded
Widget build(BuildContext context) {
FutureBuilder(
future: commentStream, //add the initialization here
....//
}
}
so future comments must be initialized when the page is first opened, then when the keyboard appears, the build will not rebuild because it has already been initiated.
i hope this helping you, im very bad at explanation lol ;D

Dynamic staggered grid with same cell height in flutter

GridView List
I'm trying to make Staggered Grid List with same image cell height
I found a plugin https://pub.dev/packages/flutter_staggered_grid_view but it didn't work for me,
I need 2nd column to get down with some space and column height should be same, what should I do?
flutter_staggered_grid_view Plugin.
Try this:
class MyHomeScreen extends StatefulWidget {
#override
_MyHomeScreenState createState() => _MyHomeScreenState();
}
class _MyHomeScreenState extends State<MyHomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Staggered Grid View with image demo"),),
body: Center(
child: sliverGridWidget(context),
),
);
}
Widget sliverGridWidget(BuildContext context){
return StaggeredGridView.countBuilder(
padding: const EdgeInsets.all(8.0),
crossAxisCount: 4,
itemCount: 10, //staticData.length,
itemBuilder: (context, index){
return Card(
elevation: 8.0,
child:InkWell(
child: Hero(
tag: index,// staticData[index].images,
child: new FadeInImage(
width: MediaQuery.of(context).size.width,
image: NetworkImage("https://images.unsplash.com/photo-1468327768560-75b778cbb551?ixlib=rb-1.2.1&w=1000&q=80"), // NetworkImage(staticData[index].images),
fit: BoxFit.cover,
placeholder: AssetImage("assets/images/app_logo.png"),
),
),
onTap: (){
//
}
)
);
},
staggeredTileBuilder: (index) => StaggeredTile.count(2,index.isEven ? 2: 3),
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
);
}
}
Output:

Flutter: Streambuilder gets called multiple times

I am working on app that displays products. The app has 4 blocks in a listview. Each block showcases the product image with its information, and can be scrolled horizontally to get more data.
I have following sample code
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return HomeState();
}
}
class HomeState extends State<Home> {
//------- other codes
#override
void initState() {
bloc.fetchNewProperties(0);
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
drawer: CustomDrawer(),
backgroundColor: Constants.scaffoldColor,
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
expandedHeight: 216,
floating: false,
titleSpacing: 0.0,
pinned: true,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Constants.gradientStart, Constants.gradientEnd]),
),
child: FlexibleSpaceBar(
centerTitle: true,
background: Swiper(
itemBuilder: (BuildContext context, int index) {
return new Image.asset(
tempBannerImages[index],
fit: BoxFit.fill,
);
},
itemCount: 2,
pagination: new SwiperPagination(),
autoplay: true,
autoplayDelay: 8000,
autoplayDisableOnInteraction: true,
),
),
),
),
];
},
body: ListView(
padding: EdgeInsets.only(
left: size.getSizePx(10), right: size.getSizePx(10)),
children: <Widget>[
newProductsBlock(),
newProductsBlock(),
newProductsBlock(),
newProductsBlock(),
newProductsBlock(), //have added widget multiple times to make stuff scrollable
],
),
),
);
}
Widget newProductsBlock() {
print("---This gets called just once---");
return Column(
children: <Widget>[
Text("New Products"),
Container(
height: 230,
child: StreamBuilder(
stream: bloc.allNewProps,
builder: (context, AsyncSnapshot<ProductsModel> snapshot) {
print("--this gets called multiple times.---");
if (snapshot.hasData) {
return buildNewPropListSwipe(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return showCirularLoadingIndicator();
},
),
)
],
);
}
}
Problem
The products are loaded successfully in these blocks. But when I scrolled all the way down and back to top. The circularloading indicator gets visible, in the first block, instead of already loaded items. When I checked i found that Streambuilder is called multiple times as I scroll down and back again up.
Can anybody help me on this, i am quite new to flutter.
Thanks
For anybody who stumble upon same issue,
i changed Parent Listview to ScrollSingleChildView and wrapped widgets into Column and it seems to get fixed now