Unnecessary Stateless Widget Rebuilds (Flutter/Dart) - Possibly StreamBuilder Related? - android-studio

I am having an issue with very high levels of widget rebuild. In particular when any navigation between pages happens.
App Background:
I am essentially building an app to create and display collections (in this case house plants). Authentication, database, and image storage are all via Firebase. This data is then delivered to widgets via streams. All my custom widgets are currently stateless.
Summary of Main Page Structure:
I have a main "library" page that displays this information. This page has a column with a list of "Group" widgets. The major widget structure for the column looks like this:
Group
Collection
PlantTile
PlantTile
Collection
PlantTile
PlantTile
PlantTile
Group
Collection
PlantTile
Collection
PlantTile
PlantTile
etc...
Obviously the number of groups, collections, and plantTiles will vary depending on what the user has submitted to the database.
Problem:
Every time I navigate between pages in the app, each plant Tile rebuilds not once, but 5 times. In the example image there are 49 of these tiles, resulting in 245 rebuilds. The plantTiles are stateless but wrapped in a StreamBuilder to display data. These widgets contain images and box shadows which seem to be very taxing on the system to rebuild.
Widget Rebuild Stats
GridView.builder wraps each plantTile with a StreamBuilder
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:plant_collector/formats/constants.dart';
import 'package:plant_collector/screens/plant.dart';
import 'package:plant_collector/widgets/dialogs/select/dialog_select.dart';
import 'package:provider/provider.dart';
import 'package:plant_collector/models/ui_builders/builders_general.dart';
import 'package:plant_collector/formats/colors.dart';
class TilePlant extends StatelessWidget {
final Map plantMap;
final String collectionID;
final List<dynamic> possibleParents;
TilePlant({
#required this.plantMap,
#required this.collectionID,
#required this.possibleParents,
});
#override
Widget build(BuildContext context) {
return GestureDetector(
onLongPress: () {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogSelect(
title: 'Move to Another Collection',
text:
'Please select the collection where you would like to move this plant.',
plantID: plantMap[kPlantID],
listBuildColumn: Provider.of<UIBuilders>(context)
.createDialogCollectionButtons(
selectedItemID: plantMap[kPlantID],
currentParentID: collectionID,
possibleParents: possibleParents,
),
);
},
);
},
child: Container(
decoration: BoxDecoration(
image: plantMap[kPlantThumbnail] != null
? DecorationImage(
image: CachedNetworkImageProvider(plantMap[kPlantThumbnail]))
: DecorationImage(
image: AssetImage(
'assets/images/default.png',
),
),
boxShadow: kShadowBox,
shape: BoxShape.rectangle,
),
child: FlatButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PlantScreen(
plantID: plantMap[kPlantID],
forwardingCollectionID: collectionID,
),
),
);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
plantMap[kPlantName] != null
? Padding(
padding: EdgeInsets.all(1.0),
child: Container(
color: Color(0x44000000),
margin: EdgeInsets.all(2.0),
padding: EdgeInsets.all(3.0),
constraints: BoxConstraints(
maxHeight: 50.0,
),
child: Text(
plantMap[kPlantName],
textAlign: TextAlign.center,
overflow: TextOverflow.fade,
style: TextStyle(
fontSize: 10.0,
color: Colors.white,
),
),
),
)
: Container(),
],
),
),
),
);
}
}
What I Would Like:
These widgets have no reason to update. The point of wrapping in the stream was that after the initial creation, a plantTile should only be updated if a stream event is delivered (the user modifies plant data). So ideally the widget rebuild of plantTile on navigation would be zero (not 5 x 49 = 245), and if the data of a plant is changed, 1 rebuild.
I have read that there may be an issue with the StreamBuilders firing at every screen build. I tried to follow other guides to solve this but I must be missing something because I can't seem to stop it.
Any help would be much appreciated! I am new to this, so hopefully I'm not doing something fundamentally wrong.

Related

the argument for the named parameter 'child' was already specified

I can't put icon and text at the same time for floatbuttom?
The error in the title.
return new Scaffold(
appBar: new AppBar(
title: new Text('Hello'),
backgroundColor: Colors.blue,
),
body: new Container(
padding: new EdgeInsets.all(22.0),
child: new Column(
children: <Widget>[
new Text("it's working, $name"),
new FlatButton(onPressed:()=> onClick('test'), child: new Icon(Icons.accessibility),child : new Text('data'))
],
)));
}
}
Your problem is inside the FlatButton widget, you are using child attribute two times.
You can put text and icon, this way:
FlatButton.icon(
icon: Icon(Icons.accessibility),
label: Text('data'),
onPressed: () {
//Code to execute when Button is clicked
},
)
I believe wrapping both children in either a row or column would solve your problem.
FlatButton can only take one widget as the child parameter.
FlatButton(
onPressed:()=> onClick('test')},
child: Row(
children: <Widget>[
new Icon(Icons.accessibility),
new Text('data'),
],
),
),

Widget not displaying on Stack and there is no Ink effect on InkResponse

I am working on my first Flutter App and I have encountered two problems.
- The Container widget housing my Image widget does not get displayed on the Stack widget.
There is no Ink effect when tapping the InkResponse container
I have tried rewriting the Container to display the image and by using Image.Network for the url instead of assets but to no avail.
final appLogo = new Container(
child: new Image(
image: new AssetImage('assets/discord.png')
),
);
List<Widget> _getApps(List apps) {
List<Widget> _appWidgets = <Widget>[];
// make a Grid tile of the Apps
for (int i = 0; i < apps.length; i++) {
_appWidgets.add(
new InkResponse(
child: Center(
child: new Stack(
children: <Widget>[
appLogo,
new Container(height: 102, width: 102, color: Colors.red),
],
)
)
),
);
}
return _appWidgets;
}
appLogo should be displayed on the red box (Container) from my expectation and there should be a Splash when I tap on the InkResponse widget.
First of all, your image needs to be added to your pubspec.yaml file like so:
flutter:
assets:
- images/
Once you have that, you can access all your images inside "/images" or any folder name inside your project you want. Now you can access your image in this way:
Image.asset("images/myimage.jpg") // again, this in an example
Bear in mind that images are not affected by the ripple material effect, only the background of it (if the image is inside a bigger container with "empty" space). Second, you need an InkWell with onTap: method in order to show the ripple, to finish everything, you need a Material widget as this one provides the necessary effects.
So, if you want to see a the ripple effect behind the image and having it inside a Stack, you'll need to do something like:
Material(
child: Stack(
children: <Widget>[
InkWell(
onTap: () {}, // The ripple only shows up if you have a onTap method.
child: Container(
height: 300, // 300 is a random value but has bigger height than the image itself.
child: Image.asset("images/myimage.jpg"),
),
),
],
),
)

Is it possible to disable shadow/overlay on dialog?

I'm wondering if there's a way to disable the shadow/overlay affect a dialog has? Basically so I can get a dialog looking like it does on the right side of this image:
My best attempt at this was to use a stack containing my custom dialog which are then toggled to be displayed or not but then I had trouble being able to scroll each custom dialog's own ListView without it messing up another. I know this goes against the Material Design guidelines but I'm trying to replicate a UI from dribble.com.
Thanks!
Edit:
I've managed to almost achieve this affect by editing the showGeneralDialog method but there's still an elevation shadow:
await showGeneralDialog(
context: context,
pageBuilder: (BuildContext buildContext,
Animation<double> animation,
Animation<double> secondaryAnimation) {
return SafeArea(
child: Builder(builder: (context) {
return AlertDialog(
content: Container(
color: Colors.white,
width: 150.0,
height: 150.0,
child: Center(child: Text("Testing"))));
}),
);
},
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context)
.modalBarrierDismissLabel,
barrierColor: null,
transitionDuration:
const Duration(milliseconds: 150));
Edit 2: Just an image to illustrate the change on the above code showing that I've so far been able to disable the dark overlay but there's still elevation on the dialog which I can't seem to get rid of:
Edit 3: I think if I'm able to change the AlertDialog in the showGeneralDialog's Builder then I can get it to work but I'm having trouble putting in something which is Material but doesn't take up the whole screen.
Got it to work! You have to create your own dialog like Widget within the Builder of the showGeneralDialog method along with setting the barrierColor to null:
await showGeneralDialog(
context: context,
pageBuilder: (BuildContext buildContext,
Animation<double> animation,
Animation<double> secondaryAnimation) {
return SafeArea(
child: Builder(builder: (context) {
return Material(
color: Colors.transparent,
child: Align(
alignment: Alignment.center,
child: Container(
height: 200.0,
width: 250.0,
color: Colors.white,
child:
Center(child: Text('Testing')))));
}),
);
},
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context)
.modalBarrierDismissLabel,
barrierColor: null,
transitionDuration: const Duration(milliseconds: 150));
Friend, set the parameter "elevation" = 0. It's work.
AlertDialog(
elevation: 0,
),
I have achieved the result using below code. Trick is barrierColor property in showDialog method which I set white color with opacity value zero and barrier shadow is vanished
AlertDialog alert = AlertDialog(
backgroundColor: Colors.transparent,
elevation: 0,
content: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Loader(),
],
),
);
showDialog(
barrierColor: Colors.white.withOpacity(0),
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: (){},
child: alert);
},
);
Just set in showDialog the barrierColor parameter to Colors.transparent.
Example:
showDialog(
context: context,
barrierColor: Colors.transparent, // Here the solution
builder: (context) => myDialog(),
);

Blur background behind dialog flutter? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I want to achieve blur background behind dialog on SimpleDialog class. What I'm looking for is something similar to this, but for flutter.
Github Android project
EDIT:
I already checked this question, but this is about the Dialog, I want to implement it on SimpleDialog.
Just wrap your Dialog inside BackdropFilter
return new BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
backgroundColor: Color(ColorResources.BLACK_ALPHA_65),
child: _dialogContent(),
)
);
Widget _dialogContent() {}//Your dialog view
I implemented blured background with showGeneralDialog method to make a blur transition as smooth as possible. Here is an example:
showGeneralDialog(
barrierDismissible: true,
barrierLabel: '',
barrierColor: Colors.black38,
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (ctx, anim1, anim2) => AlertDialog(
title: Text('blured background'),
content: Text('background should be blured and little bit darker '),
elevation: 2,
actions: [
FlatButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
transitionBuilder: (ctx, anim1, anim2, child) => BackdropFilter(
filter: ImageFilter.blur(sigmaX: 4 * anim1.value, sigmaY: 4 * anim1.value),
child: FadeTransition(
child: child,
opacity: anim1,
),
),
context: context,
);
In flutter, The dimming effect behind the dialog and bottom sheets is done using a class named 'ModalBarrier'. So what you can do is just modify the code where it dims the background.
You can easily search the file in 'IntelliJ' by using the shortcut 'Double shift'
First, you need to
import 'dart:ui' show ImageFilter;
Then in the build method change (Line: 96)
child: color == null ? null : DecoratedBox(
decoration: BoxDecoration(
color: color,
),
),
into
child: color == null ? null : BackdropFilter(
filter: new ImageFilter.blur(sigmaX: 3, sigmaY: 3),
child: Container(color: Color(0x01000000)),
),
You can change the value of 'sigma' as per your usecase.
Screenshot : Blurred Dialog
try implementing this code
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.asset('asset url', fit: BoxFit.cover),
blur(),
],
),
),
],
),
);
}
Widget blur(){
if(
//dialog pops up or is active
){
return BackdropFilter(
filter: ImageFilter.blur(sigmaX:5.0,sigmaY:5.0),
);
}
else{
return Image.asset('asset url', fit: BoxFit.cover);////if dialog not active returns an unfiltered image
}
}

Flutter: How do you make a card clickable?

I just have a simple Card like new Card(child: new Text('My cool card')) and I want to be able to click anywhere on it to run some function, except there's no onPressed method for a Card. I could add a button to the bottom, but that's not ideal for this situation.
Anyone know how to make the whole card clickable?
Flutter use composition over properties.
Wrap the desired widget into a clickable one to achieve what you need.
Some clickable widgets : GestureDetector, InkWell, InkResponse.
GestureDetector(
onTap: () => ......,
child: Card(...),
);
Flutter provides the InkWell Widget. by registering a callback you can decide what happens when user clicks on the card (called tap in flutter). InkWell also implements Material Design ripple effect
Card(
child: new InkWell(
onTap: () {
print("tapped");
},
child: Container(
width: 100.0,
height: 100.0,
),
),
),
I think you can also use InkWell apart from GestureDetector just wrap the card inside InkWell() Widget
InkWell(
onTap: (){ print("Card Clicked"); }
child: new Card(),
);
You can use Inkwell and insert splashColor which, at the click of the user, creates the rebound effect with the chosen color, on the card ..
This is mainly used in material design.
return Card(
color: item.completed ? Colors.white70 : Colors.white,
elevation: 8,
child: InkWell(
splashColor: "Insert color when user tap on card",
onTap: () async {
},
),
);
Wrap a card in GestureDetector Widget like a below:
GestureDetector(
onTap: () {
// To do
},
child: Card(
),
),
Another way is as follows:
InkWell(
onTap: () {
// To do
},
child: Card(),
),
In Flutter, InkWell is a material widget that responds to touch action.
InkWell(
child: Card(......),
onTap: () {
print("Click event on Container");
},
);
GestureDetector is a widget that detects the gestures.
GestureDetector(
onTap: () {
print("Click event on Container");
},
child: Card(.......),
)
Difference
InkWell is a material widget and it can show you a Ripple Effect whenever a touch was received.
GestureDetector is more general-purpose, not only for touch but also for other gestures.
The most preferred way is to add ListTile as Card child. Not only does ListTile contain the method onTap it also helps you in making Card interesting.
Card(
child: ListTile(
title: Text('Title')
leading: CircleAvatar(
backgroundImage: AssetImage('assets/images/test.jpg'),
),
onTap: () {
print('Card Clicked');
},
),
),
You also can insert a card into a TextButton:
TextButton clickableCard = TextButton(child: card, onPressed: onCardClick, style: [...]);
This brings the advantage, that you get some features for free. For example in Flutter Web, you get a mousover effect and the cursor changes to the hand so that ths user knows, he can click there. Other additional features can be customised using the style.
Do something on tap/click of 'child' in Flutter:-
Code:-your code looks like:
child: Card(------
------------
--------),
Step1:- Put your mouse cursor on Card then, press- Alt+Enter(in windows) select wrap with widget.
Step2:- Change your default widget into GestureDetector.
final code:-
child: GestureDetector(
onTap: YourOnClickCode(),
child: Card(------
------------
--------),
),
Most of the answers are brilliant but I just want to share mine for the one who wants to make/show a ripple effect on Tap of card or list tile.
Card(
child: TextButton(
onPressed: ()=> ...,
child: ListTile(
title: Text('title'),
),
),
);

Resources