shawn.blais

Shawn has worked as programmer and product designer for over 20 years, shipping several games on Steam and Playstation and dozens of apps on mobile. He has extensive programming experience with Dart, C#, ActionScript, SQL, PHP and Javascript and a deep proficiency with motion graphics and UX design. Shawn is currently the Technical Director for gskinner.

@tree_fortress

Flutter: How to measure Widgets

One of the trickier things to do in Flutter is to get the size of things.

Generally there are 3 use cases you might have:

  1. Measure your own context
  2. Measure your parents context
  3. Measure a child context

The first 2 are quite simple so we’ll just skim them. The 3rd is more interesting.

Measure your own size

To get your own size, you can just do:

Size s = (context.findRenderObject() as RenderBox)?.size ?? Size.zero;

Note: The first time this runs the size will be zero, Flutter needs to layout each Widget twice before we can get an accurate size.

Measure your Parents size

As most developers probably know, you can use a LayoutBuilder to get the size of your parent. Not a lot to say about this one, it’s primarily useful when you want to act in some responsive way, but rather than using full-screen dimensions (MediaQuery), you want to use the size available to the Widget.

LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints) {
     // constraints.maxWidth and constraints.maxHeight are your parent's size.
  }
}

For example, on Web and Desktop, you will use LayoutBuilder almost exclusively over MediaQuery, as most Widgets exist in a panel, pop-over or some other partial-view ui. Use this when you don’t want to layout according to the entire window size, but instead to the size of the actual panel.

For example, you may have a component that will switch to a Row when Horizontal or Column when vertical. This component wouldn’t care at all about the size of the entire window, it only cares about the room available to itself to grow.

Measure a Childs size

Measuring a child Widget in Flutter is a little more complex, but it’s certainly doable. The basic idea, is to use the “own size” approach from above but dispatch that size to any listeners.

The KISS approach here, is a simple MeasurableWidget with a callback:

class MeasurableWidget extends StatefulWidget {
  const MeasurableWidget({Key key, this.child, this.onSized}) : super(key: key);
  final Widget child;
  final void Function(Size size) onSized;

  @override
  _MeasurableWidgetState createState() => _MeasurableWidgetState();
}

class _MeasurableWidgetState extends State<MeasurableWidget> {
  bool_hasMeasured = false;
  @override
  Widget build(BuildContext context) {
    Size size = (context.findRenderObject() as RenderBox)?.size ?? Size.zero;
    if (size != Size.zero) {
      widget.onSized?.call(size);
    } else if (!_hasMeasured) {
      // Need to build twice in order to get size
      scheduleMicrotask(() => setState(()=>_hasMeasured = true));
    }
    return widget.child;
  }
}

Due to how Flutter manages layout by default, we have to render twice in order to get a valid size. This is not ideal, but also 2 renders is nothing to worry about generally.

To use, you can just do:

MeasurableWidget(child: AnyRandomWidget(), onSized: _handleWidgetSized)

In practice, you might use this sort of thing to manually Align or Offset some child widget:

Size _widgetSize = Size.zero;
Widget build(BuildContext context){
   Offset o = Offset(_widgetSize.size.width/2, _widgetSize.size.height/2);
   return Transform.translate(
      offset: o, 
      child: MeasurableWidget(child: ..., onSized: _handleWidgetSized);
   );
}

void _handleWidgetSized(Size value) => setState(()=>_widgetSize = value);

This class can then center any Widget that was passed into it, even though it knows nothing about that Widget. It just wraps it in a MeasurableWidget and waits for the callback!

Using Notifications

Another approach to this can be done with Flutter Notifications. Notifications are great because they bubble upwards, sort of like how Provider drills objects downwards, but in the other direction.

This approach is handy if you need your size to travel up multiple tiers of the application, and you don’t want to have to continually pass the Size. To do this, you would just create some custom Notification:

class WidgetMeasuredNotification extends Notification {
    WidgetMeasuredNotification(size);
    final Size size;
}

You could then create a widget that dispatches a Notification, instead of using a callback:

class _MeasurableWidgetState extends State<MeasurableWidget> {
  Widget build(BuildContext context) {
    ...
    if (size != Size.zero) {
      WidgetMeasuredNotification(size).dispatch(context);
    } else if (_buildCount == 0) {
    ...
  }
}

The final step is to just catch the Notification with a NotificationListener. We could re-write the usage example above like:

Size _widgetSize = Size.zero;
Widget build(BuildContext context){
   Offset o = Offset(_widgetSize.size.width/2, _widgetSize.size.height/2);
   return NotificationListener(
      child: MeasurableWidget(child: widget.someChild),
      onNotification => (WidgetMeasuredNotification n){
         _handleWidgetSized(n.size);
         return true;
      }
   );
}

void Function(Size value) _handleWidgetSized => setState(()=>_widgetSize = value)

That’s it! Now you have the same setup, but there is no direct coupling between the child Widget, and the Listener. You could have the Listener be multiple levels above the child, and it would still catch the size and can act on it.

You might use this sort of approach in a complex animated menu, that has some visual indicator tied to the size of the selected button. In this case you would want some sort of loose coupling between the MenuController, and the lower-level MenuButtons that are selected.

Going further with RenderBox

So, you might be looking at the above approaches, and thinking to yourself… Measure twice? Come on man!

Come On Man GIFs | Tenor
Can’t we do better?

Yes we can! Using a custom RenderObject and overriding the performLayout method, we can measure a widget in one pass!

First you make a custom RenderBox like so:

class MeasureSizeRenderObject extends RenderProxyBox {
  MeasureSizeRenderObject(this.onChange);
  void Function(Size size) onChange;

  Size _prevSize;
  @override
  void performLayout() {
    super.performLayout();
    Size newSize = child.size;
    if (_prevSize == newSize) return;
    _prevSize = newSize;
    WidgetsBinding.instance.addPostFrameCallback((_) => onChange(newSize));
  }
}

You then make a tiny StatelessWidget wrapper so you can use it:

class MeasurableWidget extends SingleChildRenderObjectWidget {
  const MeasurableWidget({Key key, @required this.onChange, @required Widget child}) : super(key: key, child: child);
  final void Function(Size size) onChange;
  @override
  RenderObject createRenderObject(BuildContext context) => MeasureSizeRenderObject(onChange);
}

With those two small classes in place, you can simple do:

MeasureSize(onChange: _handlePopOverSized, child: ...)

Hopefully this helps you in your Flutter journey and spawns a few new ideas!

Flokk – How we built a Desktop App Using Flutter

Earlier this year Google and Ubuntu approached us with an open brief, to try and create a full-scale desktop application for Linux, macOS, Web, and Windows. The result was Flokk, which we released (and open-sourced) back in July.

In this post, we’re going to dive into some of the challenges we faced, the discoveries we made, and desktop-specific features that we added.

Continue reading →

Flutter Tricks: Widget Size & Position

Occasionally in Flutter, you will need to get the size and/or position of a widget. Some common use cases for this are:

  • You need to measure something and make some decision based on that. For example, you might switch from an expanded column, to a scrolling column, at a certain height threshold.
  • You may want enable or disable a scrollbar based on the height of some content
  • The parent needs to know your position. For example if when you tap a menu button, the parent wants to animate a dot to that position, we need the button to be able to report its position somehow.
Continue reading →

Flutter: Extending State‹T›

Recently we’ve been exploring the ability to extend the base State<T> class, to add additional capabilities to our views, or writing our own custom Mixins to do the same.

Most Developers are familiar with using the various framework Mixins, like TickerProviderStateMixin but what is not commonly known, is how easy it is to create your own flavors of these. Additionally, many developers extend State<T> constantly, and write very repetitive boilerplate code, without realizing how easy it is to create their own BaseState<T> instead.

In this example, we’ll make a “base state” that provides 3 AnimatorControllers to any widget that needs to animate something. We could then apply this to any Widget in our application, and it automatically get 3 animators to play with. No setup, or teardown required.

Continue reading →

Flutter: Introducing StatsFl, an FPS monitor for Flutter

As we begin pushing Flutter to more platforms such as Desktop and Web, it is becoming increasingly important to quickly and easily measure performance of your application. While the built-in performance monitor gets the job done, it leaves a lot to be desired in terms of readability and flexibility.

As the old school Flash devs we are, we remember the days when virtually every Flash application around would use the hi-res-stats package by mrdoob (yes, that mrdoob). It was extremely helpful to catch performance issues, and make sure your application was smooth (which in those days, was a solid 24fps!).

Currently nothing like that exists in the Flutter community. To help fill this gap, we’ve created StatsFl! Available now on pub.dev: https://pub.dev/packages/statsfl

Continue reading →

Flutter: Create Custom App Themes

There is a lot to like about Flutter, but one area I’m sure no one loves, is taming the Material Theme system! With over 65(!) properties, some of which, like TextTheme, break out into 10 more sub-properties is enough to make you go crazy pretty quick.

Who wants to dive in?

Last week we looked at how you can easily implement TextStyling, and this week we’re going to dive into Color Themes. We’ll take a look at a technique that we use to implement custom app-specific themes, while still providing Material ThemeData to the core Flutter components.

Continue reading →