I have a convenience StatelessWidget that returns the appropriate widget for one of three display size breakpoints:
/// Return the most appropriate widget for the current display size.
///
/// If a widget for current display size is not available, chose the closest smaller variant.
/// A [mobile] size widget is required.
class SizeAppropriate extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables
SizeAppropriate(
this.context,
{
required this.mobile,
this.tablet,
this.desktop,
Key? key
}
) : super(key: key);
final BuildContext context;
late final Widget mobile;
late final Widget? tablet;
late final Widget? desktop;
#override
Widget build(BuildContext context) {
switch (getDisplaySize(context)) {
case DisplaySize.mobile:
return mobile;
case DisplaySize.tablet:
return tablet ?? mobile;
case DisplaySize.desktop:
return desktop ?? tablet ?? mobile;
}
}
}
I then use it like this:
SizeAppropriate(
context,
mobile: const Text('mobile'),
desktop: const Text('desktop'),
)
Is the keyword late working here as intended, building only the correct variant, or am I hogging the performance, because all variants are constructed (or am I even creating an anti-pattern)?
Should I use builder functions instead?
Edit:
This answer makes me think I'm correct. Although the last two sentences make me think I'm not correct.
When you do this, the initializer becomes lazy. Instead of running it as soon as the instance is constructed, it is deferred and run lazily the first time the field is accessed. In other words, it works exactly like an initializer on a top-level variable or static field. This can be handy when the initialization expression is costly and may not be needed.
When I do log('mobile') and log('desktop') in the respective widgets being constructed, I only get one type of message in the console.
Edit 2 – minimum working example:
This is my main.dart in a newly generated project (VS Code command >Flutter: New Project – Application). It might be worth noting, that I am testing this on Linux.
import 'package:flutter/material.dart';
import 'dart:developer';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: SizeAppropriate(
context,
mobile: const Mobile(),
tablet: const Tablet(),
desktop: const Desktop(),
),
),
);
}
}
class Mobile extends StatelessWidget {
const Mobile({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
log('mobile');
return const Text('mobile');
}
}
class Tablet extends StatelessWidget {
const Tablet({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
log('tablet');
return const Text('tablet');
}
}
class Desktop extends StatelessWidget {
const Desktop({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
log('desktop');
return const Text('desktop');
}
}
enum DisplaySize {
desktop,
tablet,
mobile,
}
DisplaySize getDisplaySize(BuildContext context) {
Size size = MediaQuery.of(context).size;
if (size.width < 768) {
return DisplaySize.mobile;
}
else if (size.width < 1200) {
return DisplaySize.tablet;
}
else {
return DisplaySize.desktop;
}
}
class SizeAppropriate extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables
SizeAppropriate(
this.context,
{
required this.mobile,
this.tablet,
this.desktop,
Key? key
}
) : super(key: key);
final BuildContext context;
late final Widget mobile;
late final Widget? tablet;
late final Widget? desktop;
#override
Widget build(BuildContext context) {
switch (getDisplaySize(context)) {
case DisplaySize.mobile:
return mobile;
case DisplaySize.tablet:
return tablet ?? mobile;
case DisplaySize.desktop:
return desktop ?? tablet ?? mobile;
}
}
}
late does not do what you want. It's only for the null-safety feature and when you do or don't get warnings about it. Those two texts get built every time regardless of environment, because they need to be there when they are passed to your widget.
If you to only build the appropriate widgets for each size when the size is known, you indeed need indeed pass two builders, one of which you call if appropriate.
For two constant Text widgets, that would be too much overhead, but I am assuming you want "heavier" widget trees for both options in the end.
Related
I am confused between these three ways of passing/building widgets:
1.
Widget _myDisplay() {
return [widgets showing content];
}
#override
Widget build(BuildContext context) {
return _myDisplay();
}
#override
Widget build(BuildContext context) {
return MyDisplay();
}
where MyDisplay is defined as such (I'm not sure if it's crucial whether MyDisplay is a StatelessWidget or a StatefulWidget):
class MyDisplay extends StatelessWidget {
const MyDisplay({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return [widgets showing content];
}
}
Widget _myDisplay = [widgets showing content];
#override
Widget build(BuildContext context) {
return _myDisplay;
}
I've read this thread comparing the first two methods, and from what I understand, using a unique, named class extending StatelessWidget or StatefulWidget allows you to use the const keyword which signifies that it will not be rebuilt when the Widget tree is rebuilt.
However, what about the 3rd method above? Is it the same as 1. or 2., or completely different? If so, how is it different and when is it preferred?
Thanks!
Type 1: helper methods
Type 2: Widgets
Type 3: Variables
For type 1 and 2, I will highly recommend to check Widgets vs helper methods | Decoding Flutter
Helper methods will rebuild everything on every state changes which can be heavy based on scenario like using animated-widgets.
Try using Widget with const constructor to get better performance,
Now for the 3rd type variable. It is totally different from helper method and widgets. This variable won't change until you handle the variable state. Test this example code, and you will find the issue where data doesn't change on type3(variable) case.
class Test3 extends StatefulWidget {
const Test3({Key? key}) : super(key: key);
#override
State<Test3> createState() => _Test3State();
}
class _Test3State extends State<Test3> {
String data = "A";
late Widget _myDisplay = Text("$data");
Widget _myDisplayMethod() => Text("$data");
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {
data = "b";
});
}),
body: Center(
child: Column(
children: [
_myDisplay, //variables doesn't update state(based on cases)
_myDisplayMethod(),
],
),
),
);
}
}
In my application I have a custom ThemeProvider implemented in InheritedWidget (default Theme provided by Flutter is a bit too rigid with regards to what a theme can be):
class ThemeProvider extends InheritedWidget {
final AppTheme theme;
const ThemeProvider({Key? key, required Widget child, required this.theme}): super(key: key, child: child);
static AppTheme of(BuildContext context) {
final provider = context.dependOnInheritedWidgetOfExactType<ThemeProvider>();
return provider?.theme ?? AppTheme.defaultTheme;
}
#override
bool updateShouldNotify(ThemeProvider oldWidget) {
return theme != oldWidget.theme;
}
}
Inside a component I can require some specific portion of the theme as needed:
class StyledIcon extends StatelessWidget {
final IconData icon;
final double? size;
final Color? color;
const StyledIcon(this.icon, {Key? key, this.size, this.color}): super(key: key);
#override
Widget build(BuildContext context) {
final theme = ThemeProvider.of(context).icon;
return Icon(
icon,
size: size ?? theme.size,
color: color ?? theme.color
);
}
}
As far as managing application theme goes, what's the benefit of passing theme data around through an InheritedWidget? Why won't a global theme object suffice?
For most applications it doesn't make much of a difference. Inherited widgets have the advantage that you can scope your theme to certain parts of your app. So if you want to scope your theme at all, use an inherited widget. If you don't care about that you can stick with a global object.
How do you pass properties to a widget class in flutter.
Here is a basic class
class ToolbarToggle extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text(label);
}}
I want to be able to call it
ToolbarToggle(label: 'a label')
Forgive the elementary question.
First you declare a label property, like this:
class ToolbarToggle extends StatelessWidget {
final String label;
#override
Widget build(BuildContext context) {
return Text(label);
}
}
final means the value is immutable, you should add it to every property on a StatelessWidget.
Now we need to declare a Constructor, here is how we do that:
ToolbarToggle(this.label);
This means that when creating a ToolbarToggle, we can do this: ToolbarToggle('some label');. In order to make parameters named, we need to declare the constructor like so:
ToolbarToggle({this.label});
Now it is possible to call ToolbarToggle(label: 'my label');. But this will give an error, because it's possible you don't actually pass any value when calling the constructor. To fix this, you should either make it a required parameter, or give it a default value:
ToolbarToggle({required this.label});
ToolbarToggle({this.label=''});
Here is the final class code:
class ToolbarToggle extends StatelessWidget {
ToolbarToggle({required this.label});
final String label;
#override
Widget build(BuildContext context) {
return Text(label);
}
}
class ToolbarToggle extends StatelessWidget {
final String? label;
const ToolbarToggle({this.label, Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text(label ?? 'label is null');
}}
Like this, you can pass properties,
Its recommended to use const constructor and a key param ( to uniquely identify incase)
You missed using the label
class ToolbarToggle extends StatelessWidget {
final String label;
ToolbarToggle({required this.label});
#override
Widget build(BuildContext context) {
return Text(label);
}}
I am quite puzzled about the values being passed in the class constructor not being available in the Widget.
I am passing the value of the cards in the widget constructor, but when debugging it and after they are build the Text widgets do not have any text.
Initializing the Widget with the values.
Debugger shows the cardValue fields with no value.
Empty Widget:
This should work:
class PockerCard extends StatefulWidget {
final String cardValue;
const PockerCard({Key key, this.cardValue}) : super(key: key);
#override
_PockerCardState createState() => _PockerCardState();
}
class _PockerCardState extends State<PockerCard> {
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.cardValue),
);
}
}
import 'package:flutter/material.dart';
class ThemeSwitcher extends InheritedWidget {
final _ThemeSwitcherWidgetState data; // We'll use ThemeSwitcher to get access to the current state of ThemeSwitcherWidget
const ThemeSwitcher({
Key key,
#required this.data,
#required Widget child,
}) : assert(child != null),
super(key: key, child: child);
static _ThemeSwitcherWidgetState of(BuildContext context) { //This method returns the current state of the ThemeSwitcherWidget. This will be used down the tree
return (context.dependOnInheritedWidgetOfExactType(ThemeSwitcher)
as ThemeSwitcher)
.data;
}
#override
bool updateShouldNotify(ThemeSwitcher old) {
return this != old;
}
}
class ThemeSwitcherWidget extends StatefulWidget {
final bool initialDarkModeOn; // this is the initial state of the variable
final Widget child; // child to which this boolean variable should be propagated upon change. This will be our app in this case
ThemeSwitcherWidget({Key key, this.initialDarkModeOn, this.child})
: assert(initialDarkModeOn != null),
assert(child != null),
super(key: key);
#override
_ThemeSwitcherWidgetState createState() => _ThemeSwitcherWidgetState();
}
class _ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
bool isDarkModeOn;
void switchDarkMode() { //method used to toggle dark mode during the runtime of the app
setState(() {
isDarkModeOn = !isDarkModeOn;
});
}
#override
Widget build(BuildContext context) {
isDarkModeOn = isDarkModeOn ?? widget.initialDarkModeOn; // this is the build method which would build the widget tree with the above info
return ThemeSwitcher(
data: this,
child: widget.child,
);
}
}
Too many positional arguments: 0 expected, but 1 found.
Try removing the extra positional arguments, or specifying the name for named arguments.
This is the Error I am continuously facing the issue after trying many methods.
I would like to know how would this problem can be solved as I am not getting any good solution from searches.
Return the following statement in _ThemeSwitcherWidgetState of(BuildContext context) method of your code:
return (context.dependOnInheritedWidgetOfExactType<ThemeSwitcher>()).data;