Standardizing sizes across application, but still use `const` - flutter

I'd like to standard size across a flutter application to comfort to a 4 pt grid. Here's one example of how this could be done:
class Spacing {
const Spacing(double val) : points = val * 4;
final double points;
}
class PtPadding extends Padding {
PtPadding({Spacing padding, Widget child}) : super(padding: padding.points, child: child);
}
PtPadding(padding: Spacing(4), child: Text('Hello'));
// or just with regular old Padding
Padding(padding: Spacing(4).points, child: Text('Hello'));
This is great, but it seems I forgo the ability to const my specialized PtPadding forces developers to use Spacing. On the other hand, just using Spacing in a constructor and accessing the points, prevents any widget from being "const"able. So it seems like I have to take a performance hit if I want to implement this spacing in my system.
I could have a class with static const members that point to doubles, but then I'm restrained to the sizes available (ie I can only have so many static members) and I also don't get the benefits of type restrictions.
I'm wondering if anyone else has thoughts in how I might approach this.
For what it's worth, I understand why Spacing(4).points is not a const (methods inherently aren't consts), but not sure how to get around this.

The problem is, you are extending Padding. Widgets are not made to be extended. Instead, you should use composition.
class Spacing {
const Spacing(double val) : points = val * 4;
final double points;
}
class PtPadding extends StatelessWidget {
const PtPadding({
Key key,
#required this.padding,
this.child,
}) : super(key: key);
final Spacing padding;
final Widget child;
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(padding.points),
child: child,
);
}
}

Related

Flutter Reorder-able Widgets Size

I am trying to create a Flutter page with reorder-able widgets of different sizes. The closest analogy is like Android/iOS home screens with widgets.
Both of these OS' can be have apps (1x1 size), small widgets (4x1), and even large widgets (4x4) on the same page. More so, when a large app takes up the full width of a page, it re-orders the widgets below/above it as to not interfere with the UI.
I am trying to do the same with Flutter, and hopefully make is so that user's can move widgets within my application the same way they do with their mobile operating systems.
The most similar StackOverFlow question to this one can be found here -- but its quite different.
Prerequisite packages:
reorderable_widgets.dart
custom_sizes.dart
The closest I got so far is with these two packages. But, I can't seem to figure the next steps after making this:
// imported packages
import 'package:flutter/material.dart';
import 'custom_sizes.dart';
import 'reorderable_widgets.dart';
class Demo extends StatelessWidget {
const Demo({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const Scaffold(
backgroundColor: Colors.grey,
body: CustomWidgetGrid(),
);
}
}
class CustomWidgetGrid extends StatefulWidget {
const CustomWidgetGrid({Key? key,}) : super(key: key);
#override
State<CustomWidgetGrid> createState() => _CustomWidgetGridState();
}
class _CustomWidgetGridState extends State<CustomWidgetGrid> {
final items1 = fillWithCustomWidgets1();
final items2 = fillWithCustomWidgets2();
final items3 = fillWithCustomWidgets2();
#override
Widget build(BuildContext context) {
return CustomScrollView(
clipBehavior: Clip.hardEdge,
slivers: <Widget>[
CustomSliverReorderableGrid(maxExtent: MediaQuery.of(context).size.width/(items2.length), children: items2,),
CustomSliverReorderableGrid(maxExtent: MediaQuery.of(context).size.width/items1.length, children: items1,),
],
);
}
}
This has already solved:
Having responsive, moveable widgets that save their end state
Providing padding around each widget
But the problem that I'm running at is that all widgets are the same size. Messing with the source code for the package has been a bit of pain, and I think that there is any easy solution that I missed (or don't know about).
Any thoughts would be greatly appreciated!

When should you prefer `Widget` inheritance over composition in Flutter?

I keep reading things like this post explaining how Flutter heavily prefers composition over inheritance. While I partially understand why, I question what to do in scenarios where this practice becomes verbose. Plus, in Flutter's internal code, there's inheritance all over the place for built-in components. So philosophically, there must be scenarios when it is okay.
Consider this example (based on a real Widget I made):
class MyFadingAnimation extends StatefulWidget {
final bool activated;
final Duration duration;
final Curve curve;
final Offset transformOffsetStart;
final Offset transformOffsetEnd;
final void Function()? onEnd;
final Widget? child;
const MyFadingAnimation({
super.key,
required this.activated,
this.duration = const Duration(milliseconds: 500),
this.curve = Curves.easeOut,
required this.transformOffsetStart,
this.transformOffsetEnd = const Offset(0, 0),
this.onEnd,
this.child,
});
#override
State<MyFadingAnimation> createState() => _MyFadingAnimationBuilder();
}
class _MyFadingAnimationBuilder extends State<MyFadingAnimation> {
#override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: widget.duration,
curve: widget.curve,
transform: Transform.translate(
offset: widget.activated ?
widget.transformOffsetStart : widget.transformOffsetEnd,
).transform,
onEnd: widget.onEnd,
child: AnimatedOpacity(
duration: widget.duration,
curve: widget.curve,
opacity: widget.activated ? 1 : 0,
child: widget.child
),
);
}
}
The goal of MyFadingAnimation is to perform both a translation and opacity animation on a Widget simultaneously. Great!
Now, let's say I wanted to make some "shortcuts" or "aliases" to this widget, like MyHorizontalAnimation for fading in horizontally, or MyVerticalAnimation for fading in vertically. Using composition, you would have to create something like this:
class MyHorizontalAnimation extends StatelessWidget {
final bool activated;
final Duration duration;
final Curve curve;
final double offsetStart;
final void Function()? onEnd;
final Widget? child;
const MyHorizontalAnimation({
super.key,
required this.activated,
this.duration = const Duration(milliseconds: 500),
this.curve = Curves.easeOut,
required this.offsetStart,
this.onEnd,
this.child,
});
#override
Widget build(BuildContext context) {
return MyFadingAnimation(
activated: activated,
duration: duration,
curve: curve,
transformOffsetStart: Offset(offsetStart, 0),
onEnd: onEnd,
child: child,
);
}
}
That seems... very verbose to me. So my initial thought was "well, maybe I should just try extending the class anyway..."
class MyHorizontalAnimation extends MyFadingAnimation {
final double offsetStart;
MyHorizontalAnimation({
super.key,
required super.activated,
super.duration,
super.curve,
this.offsetStart,
super.onEnd,
super.child,
}) : super(
transformOffsetStart: Offset(offsetStart, 0),
);
}
To me this looks cleaner. Plus it carries the added benefit that if I added functionality/props to MyFadingAnimation, it's almost automatically integrated into MyHorizontalAnimation (with the exception of having to add super.newProp). With the composition approach, I'd have to add a new property, possibly copy/maintain a default, add it to the constructor, and by the time I'm done it just feels like a chore.
My main issue with using inheritance though (and this is probably really petty) is I can't have a const constructor for anything except my base widget, MyFadingAnimation. That, coupled with the strong discouragement of inheritance, makes me feel like there's a better way.
So, to sum everything up, here are my two questions:
How should I organize my code above to have const Widgets that redirect to other "base" Widgets?
When is it okay to use inheritance over composition? Is there a good rule of thumb for this?
I wouldn't worry about the lack of const in your redirecting constructors - after all, the composition example also lacks a const in the inner MyFadingAnimation construction. It's impossible to make a const Offset with an unknown integer argument, so this is an unavoidable language limitation.
On the topic of composition vs inheritance, there's another solution for your usecase: Secondary constructors in the base class. This pattern is used all over the framework - look at SizedBox, for example.
Do note that this style does introduce some repetitiveness when it comes to default argument values, however.
class MyFadingAnimation extends StatefulWidget {
final bool activated;
final Duration duration;
final Curve curve;
final Offset transformOffsetStart;
final Offset transformOffsetEnd;
final void Function()? onEnd;
final Widget? child;
const MyFadingAnimation({
super.key,
required this.activated,
this.duration = const Duration(milliseconds: 500),
this.curve = Curves.easeOut,
required this.transformOffsetStart,
this.transformOffsetEnd = const Offset(0, 0),
this.onEnd,
this.child,
});
MyFadingAnimation.horizontal({
super.key,
required this.activated,
this.duration = const Duration(milliseconds: 500),
this.curve = Curves.easeOut,
required double offsetStart,
this.onEnd,
this.child,
}) : transformOffsetStart = Offset(offsetStart, 0),
transformOffsetEnd = const Offset(0, 0);
#override
State<MyFadingAnimation> createState() => _MyFadingAnimationBuilder();
}
Why use composition over inheritance?
Optimization
As you have mentioned, when using composition, the outer widget can feature a const constructor, performing calculations in build rather than in the constructor itself. This is useful in multiple ways:
const is contagious. Any widgets that use your widget can be declared const as well, leading to significant benefits with large widget trees.
No calculations are performed unless the widget is actually used.
Take a widget that crossfades between two child widgets, for example. There are times when one child will never be built. Performing calculations in the build method allows for cheap construction of the child widget that may never be used.
Types don't matter
The Flutter style guide states the following:
Each API should be self-contained and should not know about other features.
Many Widgets take a child. Widgets should be entirely agnostic about the type of that child. Don’t use is or similar checks to act differently based on the type of the child.
For example, many widgets that have a text field accept any Widget, rather than a Text widget.
If this instruction is followed, one of the biggest features of inheritance is irrelevant: inherited public APIs. Widgets should not (usually) be used for anything other than being passed around and returned. What reason is there to use inheritance?
Flexibility
Using inheritance only allows a single, specifically typed child to be used. What if you wish to make the redirecting widget more complex later?
Perhaps, for example, you realize that the HorizontalFadingAnimation could use some added animated padding for a better visual effect.
Wrapping the child widget in another would require a complete rewrite of the outer widget, along with a breaking API change (as the widget's type would have to change).

Is there a way to specify function parameters and return value as const in dart?

I wrote an extension function to add SizedBox between every child in Column or Row to add space between child, instead of putting SizedBox between every child, for which I didn't find any other way around.
Column(
children: [
// widgets
].setSpace(height: 10),
)
Row(
children: [
// widgets
].setSpace(width: 10),
)
So here List<Widget> setSpace({double? height, double? width}) takes height or width for the SizedBox used between child. But since height and width are not const I cannot use const SizedBox. So is there any way in dart to say that both the parameters and the return type will ALWAYS be cosnt? like const List<Widget> setSpace({const double? height, const double? width}) like C/C++?
I don't think that's possible, mostly because const can be applied only on constructors and fields, not on generic functions.
Maybe you can achieve that by creating your own widget that adds the SizedBox in its build method, and create a const constructor.
EDIT: here's a piece of code of mine of a custom widget with a const constructor.
class UnlockPage extends StatefulWidget {
final String pcData;
const UnlockPage({Key? key, required this.pcData}) : super(key: key);
#override
Widget build(BuildContext context) {
[...]
}
}
EDIT 2: here's a piece of code tested in DartPad. I don't think it can get better than this.
class SpacedColumn extends StatelessWidget {
final double height;
final List<Widget> children;
const SpacedColumn({required this.height, required this.children});
#override
Widget build(BuildContext context) {
var actualChildren = <Widget>[];
for (var child in children) {
actualChildren.add(child);
actualChildren.add(SizedBox(height: height));
}
return Column(
children: actualChildren,
);
}
}
You can't. As you pass a value this one can be different from one call to others.
Notice that const as not the same signification on Flutter than on other languages.
With Flutter it indicates to the rendering engine that the widget or the method is always the same and that the rendering engine is not obliged to rebuild this Widget when rebuilding the screen.
The keyword that act as const in other languages is final
In Dart language const doesn't mean the same as in other languages. You should use final if you don't want to change the values later.

What is the best way to reuse a widget in Flutter?

In my app, I need to reuse a same divider more than 20 times.
Which way should I follow for best memory performance?
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);
}
The second one. From Dart's website:
Const means that the object's entire deep state can be determined entirely at compile time and that the object will be frozen and completely immutable. (...) [Const objects] are canonicalized. This is sort of like string interning: for any given const value, a single const object will be created and re-used no matter how many times the const expression(s) are evaluated.

Pass parent state to generic child widget

I'm building a flutter app, and I have built a customized AppBar widget for it. This appbar has a SearchBar widget, which calls whatever callback is passed to it onChange. Now, there are multiple screens that use this SearchBar, and each of them will do something different with the user input. But I've noticed that on each of the screens that use the appbar, I'd have to use a state to control the SearchBar inputted text. So, I'm trying to not have to create the state for every screen, and have a Widget that wraps my screens, and controls the input in it's state, and passes it's state down to the child I provide to this apps. This would be similar to React's Higher Order Components, which wrap another component and can pass props to it.
This seems to me like a good design pattern, but I don't know how to implement it. Since the child widget that I would pass to this second order component that would wrap my screens, won't be getting any info from it, the child is simply passed as a widget (note: the code is also doing other stuff, working as a general wraper to replace similar repetitive code in all of my screens):
class CustomScaffold extends StatefulWidget {
final ScreenInfo appBarInfo;
final Builder child;
final Widget bottomBarApp;
final Widget appBar;
final EdgeInsetsGeometry padding;
const CustomScaffold({
#required this.child,
Key key,
this.bottomBarApp,
this.appBar,
this.padding,
this.appBarInfo,
}) : super(key: key);
#override
_CustomScaffoldState createState() => _CustomScaffoldState();
}
class _CustomScaffoldState extends State<CustomScaffold> {
String searchTerm = '';
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
widget.appBar ??
GradientAppBar(
title: widget.appBarInfo.label,
searchBar: SearchBar(
callback: (String input) {
setState(() {
searchTerm = input;
});
},
placeholder: widget.appBarInfo.searchPlaceholder,
),
),
Padding(
padding: widget.padding,
child: widget.child,
),
],
),
),
bottomNavigationBar: widget.bottomBarApp ?? CustomBottomBarNavigator(),
);
}
}
I'm thinking that I could passa builder instead of a widget as the child, but I'm not sure how this would work. Also, I'm still learning bloc in general, so I'm not sure if it would be a good idea to use bloc for this. I'm guessing bloc's purpose is a little different, and would complicate this specific pattern.
Does this idea make sense? What would be the best way to implement it?
Thanks in advance.