How to reuse widget styles properly in flutter? - flutter

Coming from frontend webframeworks like angular, react and vue I am struggling to find the best way to write reusable widget styles. Let me demonstrate the problem with an example.
Lets say we have this Widget:
Container(
width: 25,
height: 10,
decoration: BoxDecoration(
color: const Color(0xff7c94b6),
border: Border.all(
color: Colors.black,
width: 8.0,
),
),
child: /* some custom widget */,
);
Now lets say I want to make the Container properties like width, height etc. changable by parameters. If a certain parameter for a property is not passed it should use its default value, like this:
class CustomWidget extends StatelessWidget {
final double width;
final double height;
final BoxDecoration decoration;
const CustomWidget ({
Key key,
this.width = 25,
this.height = 10,
this.decoration = /* default decoration */
/* possibly even more properties */
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
decoration: decoration,
child: /* some custom widget */
}
}
Obviously, there could be more properties which would lead to more and more boilerplate. Also what do you do, if the container does not have a decoration by default? Should you pass a custom Container always? Also consider that the Container could be nested further down the widget tree.
There must be a good solution, I just can't think of one, probably because my thoughts are biased because of my experience with frontend development. In web project you simply pass the component/widget custom css-classes to overwrite the styles (e.g. a parameter containerClasses). How do you do it properly in flutter?
EDIT: In essence my question is: Is there an equivalent to a css-class in flutter? Or: Whats the best way to make a custom widget's style totally customizable by parameters. I feel like i have to write every single property by hand.
In react you have an interface for all html elements (e.g. div, input etc.) and their props (e.g. for an input-element you have an interface with value, class, type etc.), which you can use to define what parameters one can pass to customize the component/widget.

Flutter style behaves similarly to Vue scoped styled / React "styled-component" or React native in general:
There's no "global style" in these scenarios. Instead, you use composition to obtain the desired result.
In a sense, you have one StatelessWidget for each "CSS class", instead of one big StatelessWidget with many parameters.
For example, say we want to split a "red background + border radius" into reusable styles, then we'd typically have two widgets:
RedBackground
MyBorder
where you would then be able to use them independently:
RedBackground(
child: Text('hello world'),
)
or together:
RedBackground(
child: MyBorder(
child: Text('hello world'),
),
)

Related

Invalid constant value in const constructor of Flutter/Dart class

I want to create a bunch of const colored boxes. The following code works:
class MyBox extends SizedBox {
const MyBox(Color color, {Key? key}) // [*1] color parameter
: super(key: key,
width: 20,
height: 40,
child: const DecoratedBox(
decoration: BoxDecoration(
backgroundBlendMode: BlendMode.multiply,
borderRadius: BorderRadius.all(Radius.circular(10)),
color: Colors.green, // [*2] all boxes are green
// color: color, // [*3] this is what I want
),
),
);
}
[...]
static const mbRed = MyBox(Colors.red);
static const mbYellow = MyBox(Colors.yellow);
static const mbBlue = MyBox(Colors.blue);
But notice that the color parameter at line [*1] is not actually used. If I replace line [*2] with line [*3], I am told this is an Invalid constant value. (Note that the Color class are immutable 32-bit values.)
Somehow there must be a way to pass a color parameter to a const constructor. What am I doing wrong?
And if this is the wrong way to go about it, what is a succinct means of creating a bunch of boxes, identical except for color? Thanks!
You can't. When you declare MyBox's constructor as const, that means that the constructor can be used to create a compile-time constant, not that it must be. Meanwhile, when that constructor itself invokes const DecoratedBox, you're stating that DecoratedBox is guaranteed to be a compile-time constant, but you cannot guarantee that. For example, MyBox could have been constructed with a Color determined at runtime.
Unfortunately Dart has no way to recursively say "try to invoke this constructor as const if possible". You will have to declare the MyBox constructor as a non-const constructor.

which is better for performance && #memory

in my app i need a same divider re-use more than 20+ time . so which way should i follow !!!!!!!
way 1 ........
class DividerX extends StatelessWidget {
const DividerX({ Key? key }) : super(key: key);
#override
Widget build(BuildContext context) {
return Divider(
color: Colors.green,
height: 22,
);
}
}
way 2 .......................
class DividerX {
const DividerX._();
static const Widget divider = Divider(
color: Colors.green,
height: 22,
);
}
which is better for #performance && #memory
Both of those classes are optimized for good performance as they use the const property to tell flutter to update only widgets in the tree that need to be updated. Constants are immutable so flutter doesn't bother updating them. That saves resources which makes your UI fast and snappy overall. However, there are mild advantages to the second class for 2 reasons:
It's static and doesn't need to create instance to access its widget. In terms of performance, its a bit better compared to the second class which creates its own instance.
It doesn't call additional build function like the first class. This is good because it avoids redundancy, which is the objective of your initial reason for implementing this solution anyways.

Best way to allow a bit of overscroll in a CustomScrollView

The UI I'm making usually starts with the bottom sliver scrolled all the way in, so that its top is at the top of the view. But also:
Needs an extra empty space at the top, in case the user wants to pull the content down so that they can reach it without moving their hand from the bottom of the phone (this is a really basic ergonomic feature and I think we should expect to see it become pretty common soon, first led by apps moving more of their key functionality to the bottom of the screen, EG, Firefox's url bar.) (Currently, I'm using the appBar sliver for this, but I can imagine a full solution not using that)
Might need extra empty space at the bottom, whenever the content in that bottom sliver wont be long enough to allow it to be scrolled in all the way. It will seem buggy and irregular otherwise. Ideally I'd impose a minHeight so that the bottom sliver will always at least as tall as the screen, but it's a sliver, so I'm pretty sure that's not possible/ugly-difficult.
The avenue I'm considering right now is, ScrollPhysics wrapper that modifies its ScrollMetrics so that maxExtent and minExtent are larger. As far as I can tell, this will allow the CustomScrollView (given this ScrollPhysics) to overscroll. It feels kinda messy though. It would be nice to know what determines maxExtent and minExtent in the first place and alter that.
Lacking better options, I went ahead with the plan, and made my own custom ScrollPhysics class that allows overscroll by the given amount, extra.
return CustomScrollView(
physics: _ExtraScrollPhysics(extra: 100 * MediaQuery.of(context).devicePixelRatio),
...
And _ExtraScrollPhysics is basically just an extended AlwaysScrollable with all of the methods that take ScrollMetrics overloaded to copy its contents into a ScrollMetric with a minScrollExtent that has been decreased by -extra, then passing it along to the superclass's version of the method. It turns out that adjusting the maxScrollExtent field wasn't necessary for the usecase I described!
This has one drawback, the overscroll glow indicator, on top, appears at the top of the content, rather than the top of the scroll view, which looks pretty bad. It looks like this might be fixable, but I'd far prefer a method where this wasn't an issue.
mako's solution is a good starting point but it does not work for mouse wheel scrolling, only includes overscroll at the top, and did not implement the solution to the glow indicator problem.
A more general solution
For web, use a Listener to detect PointerSignalEvents, and manually scroll the list with a ScrollController.
For mobile, listening for events is not needed.
Extend a ScrollPhysics class as mako suggested but use NeverScrollableScrollPhysics for web to prevent the physics from interfering with the manual scrolling. To fix the glow indicator problem for mobile, wrap your CustomScrollView in a ScrollConfiguration as provided by nioncode.
Add overscroll_physics.dart from the gist.
Add custom_glowing_overscroll_indicator.dart from the other gist.
GestureBinding.instance.pointerSignalResolver.register is used to prevent the scroll event from propogating up the widget tree.
Example
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:my_project/custom_glowing_overscroll_indicator.dart';
import 'package:my_project/overscroll_physics.dart';
class OverscrollList extends StatelessWidget {
final ScrollController _scrollCtrl = ScrollController();
final double _topOverscroll = 200;
final double _bottomOverscroll = 200;
void _scrollList(Offset offset) {
_scrollCtrl.jumpTo(
_scrollCtrl.offset + offset.dy,
);
}
#override
Widget build(BuildContext context) {
return Container(
height: 300,
decoration: BoxDecoration(border: Border.all(width: 1)),
child: Listener(
onPointerSignal: (PointerSignalEvent event) {
if (kIsWeb) {
GestureBinding.instance.pointerSignalResolver.register(event, (event) {
_scrollList((event as PointerScrollEvent).scrollDelta);
});
}
},
child: ScrollConfiguration(
behavior: OffsetOverscrollBehavior(
leadingPaintOffset: -_topOverscroll,
trailingPaintOffset: -_bottomOverscroll,
),
child: CustomScrollView(
controller: _scrollCtrl,
physics: kIsWeb
? NeverScrollableOverscrollPhysics(
overscrollStart: _topOverscroll,
overscrollEnd: _bottomOverscroll,
)
: AlwaysScrollableOverscrollPhysics(
overscrollStart: _topOverscroll,
overscrollEnd: _bottomOverscroll,
),
slivers: [
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.blue),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.yellow),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.red),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.orange),
),
],
),
),
),
);
}
}
dartpad demo
Mobile result:

How do I make a child widget expand to fill a parent container inside of a stack when the child has no parameters to alter its layout?

I'm building a card game and using flame to pull the cards from a sprite sheet. The problem is that I set the width and height of the Container that holds the SpriteWidget, but the SpriteWidget expands to either the width or the height of the container, but not both. I want it to expand/stretch to be the same size as the parent container. Unfortunately, the SpriteWidget really has no parameters that could be used to change its size.
I've spent several hours scouring the internet for a solution and tried a number of widgets including FittedBox, Flex, Positioned.fill, etc., but I'm unable to achieve the desired effect. How can I make the SpriteWidget stretch to fill its parent when it has no parameters to do so?
class _PlayerHandPortraitLayout
extends WidgetView<PlayerHand, _PlayerHandController> {
#override
final state;
const _PlayerHandPortraitLayout(this.state) : super(state);
#override
Widget build(BuildContext build) {
return Stack(
children: state.displayHand().asMap().entries.map((cardItem) {
var index = cardItem.key;
var card = cardItem.value;
return Positioned(
left: index * CARD_OVERLAP_OFFSET,
child: Draggable<Container>(
childWhenDragging: Container(),
child: Container(
color: Colors.purple,
width: state.cardWidth,
height: state.cardHeight,
child: SpriteWidget(
sprite: state.spriteImages[card.suite.index][card.value.index],
),
),
feedback: Container(
color: Colors.yellow,
width: state.cardWidth,
height: state.cardHeight,
child: SpriteWidget(
sprite: state.spriteImages[card.suite.index][card.value.index],
),
),
),
);
}).toList(),
);
}
}
actually this will be not possible, SpriteWidget is designed to expand as long as it fits on the smallest dimension available on its parent, you can check on it source code here.
This is done so the Sprite will not get distorted when its parent has a different aspect ratio than the ratio of the Sprite.
If you have an use case where you would want the Sprite to get intentionally distorted, please open an issue on the Flame repository explaining the case, and we can try to take a look on it.

Flutter/dart and static Widget methods. bad pattern?

I have a ui.dart file and UIBuilder.class in it:
class UI {
static Widget buildButton(String text, VoidCallback onPressed, {double width, ...}) {
return SizedBox(
width: width,
child: RaisedButton(
...
onPressed: onPressed));
}
...
static Widget buildOtherWidget(...)
....
}
Then just call it in a lot of Screen/Page:
var btn = UI.buildButton(..);
Is it a bad pattern in flutter/dart? If yes, how can I change it?
I think it can be much more efficient if you are creating a separate file for creating all your reusable widgets like,
import 'package:flutter/material.dart';
Widget buildButton(String text, VoidCallback onPressed, {double width, ...}) {
return SizedBox(
width: width,
child: RaisedButton(
...
onPressed: onPressed));
}
Widget buildSecondWidget(){
// block of code
}
Widget buildThirdWidget(){
// block of code
}
This way you can as many global widgets you want. You just need to import this file and you can access any of the reusable widget and there will be not a static reference throughout the app lifecycle.
So i think approach will be more efficient than creating stateful widgets inside a class.
It's not clear to me if this perhaps breaks the change detection. If you break the change detection, you'll get either very inefficient code (too many builds) or broken code (not enough builds).
no this could not be a bad pattern as it's not a bad pattern to create a custom widget. both your helper static methods and custom widgets are just creating a new instance of a widget. so it is completely fine to use static methods to create new widgets all over your app screens
As long as your static methods are pure functions, it is not a bad practice to do this.