You learned the basics of Flutter. You already built a project of your own. But you feel it, this sensation - it is time to level up. Do not worry, we're not going too fast but a step at a time.
🎯 In this tutorial you will understand
- how to create your own custom widget
- how to use typdef
- how to add alternative building methods to your custom widget
Level 1 - 🛶
The first approach we all learned was to just place a Scaffold
widget and continue from there.
main.dart
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
final container = Container(color: Colors.red, width: 100, height: 100);
return MaterialApp(
title: 'Bare Scaffold',
home: Scaffold(body: container),
);
}
}
and the result is below:
We immediately notice a problem: part of the red container is hidden beneath the status bar. Here the use of SafeArea is useful:
main.dart
...
return MaterialApp(
title: 'Scaffold with Safe Area',
home: Scaffold(
body: SafeArea(
child: container,
),
),
);
...
that yelds the following:
Here you catch the first drawback: following this pattern, in any further screen, you will have to repeat this code. I do not know you, but surely I do not like the repeat myself.
Level 2 - ⛵
Somewhere you heard of the DRY concept. Let's use it.
custom_scaffold.dart
import 'package:flutter/material.dart';
class CustomScaffold extends StatelessWidget {
final Widget body;
const CustomScaffold({this.body});
@override
Scaffold build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: body,
),
);
}
}
so that we can instantiate it like so:
main.dart
class App extends StatelessWidget {
final container = Container(color: Colors.red, width: 100, height: 100);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Custom Scaffold .alternative',
home: CustomScaffold(body: container));
}
}
Nothing changed on the output. But we spared some lines and potentially, we could spare much more of them (think at dynamically injected providers).
But now we want to find a way to easily operate on layouts and more specifically design them according to their purpose.
Level 3 - 🚤
Avoiding depriving ourselves of what we have built, let's implement an alternative way of creating the output.
LayoutBuilder
is a good candidate since it gives us a wide range of constraints. But this use case is slightly different from one adopted in SafeArea
. LayoutBuilder
creates the output not by injecting the child; it actually uses a builder function that puts comfortable tools in our hand, constraints (other than context).
So you try to add a builder method to your own CustomScaffold
, and it will also easily provide you with constraints (all without losing the fundamental functionality of SafeArea).
The first step is to create a signature for a function that will generate a Widget. Something that you normally put before params in function's declaration such as String
, int
, or myRandomWidget
.
custom_scaffold.dart
typedef CustomBuilder = Widget Function(
BuildContext context,
double x,
double y,
);
Now get to the actual CustomScaffold. Similarly to how we defined a final Widget body
(therefore also defined in the constructor), we also can define a final CustomBuilder builder
. For those new to typdef
s it is like adding a Function
property but a more specific function, precisely defined by us.
custom_scaffold.dart
class CustomScaffold extends StatelessWidget {
final Widget body;
final CustomBuilder builder; #1
const CustomScaffold({this.body, this.builder}); #2
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: LayoutBuilder(builder: (context, constrains) {
return builder(context, constrains); #3
}),
),
);
}
}
Adding a LayoutBuilder
in the stack, we are provided with constraints that we can pass to our builder function (eventually you can consider modulating them or mixing them with something else to create something new, but for the sake of simplicity I leave this honor to you).
There is it. I don't know you, I felt sincere happiness the first time I used a HOF. Let's wrap to where we use it, so to say.
main.dart
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Custom Scaffold .alternative',
home: CustomScaffold(
builder: (context, constraints) {
return Container(
color: Colors.blue,
width: constraints.maxWidth / 2,
height: constraints.minHeight / 3,
);
},
),
);
}
}
We give it a builder (to keep in mind that a Scaffold lives without problems even without a body, (🧟), it's not @required
- and it gives as context and constraints. It seems fair to me.
And the result:
You see how easily you can arrange the layout, don't you?
Level 4 - 🛥
Do you know how you can use ListView()
but also ListView.builder()
? It's easy to implement.
custom_scaffold.dart
class CustomScaffold extends StatelessWidget {
final Widget body;
final CustomBuilder builder;
const CustomScaffold({this.body, this.builder});
static Widget responsive({CustomBuilder builder}) {
return _buildSafeScaffold(
child: LayoutBuilder(builder: (context, constraints) {
return builder(context, constraints);
}),
);
}
static _buildSafeScaffold({Widget child}) {
return Scaffold(
body: SafeArea(
child: child,
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: body,
),
);
}
}
Note how the regular build method (the one with the
@override
meta) is untouched - we could still instantiateCustomScaffold()
and pass to it a body.
But we also get an alternative, CustomScaffold.responsive()
, that gives us the constraints:
main.dart
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Custom Scaffold .responsive',
home: CustomScaffold.responsive(
builder: (context, constraints) {
return Container(
color: Colors.yellow,
width: constraints.maxWidth / 4,
height: constraints.maxHeight / 2,
);
},
),
);
}
}
Now that you get how to "hide" different recurring features behind your custom widget, I leave you free to experiment on your own.
We certainly won't make code history with this ability,
but if you have come this far it is very likely that this possibility was not very familiar to you. So it's a step forward. And that's enough.
Top comments (0)