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: 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: Simplify Platform & Screen Size Detection

In the never-ending quest to reduce boilerplate and DRY up our code in Flutter, we have noticed that using the MediaQuery class can be a bit cumbersome, and it’s also missing a couple of key pieces of information.

The issues we see are:

  • MediaQuery.of(context) is a bit verbose on its face
  • Checking for orientation especially is too long:
    bool isLandscape = MediaQuery.of(context).orientation == Orientation.landscape
  • There is no diagonal size parameter, so you can’t easily get the true screen size of the device, helpful for determining your form factor
  • There is no way to get the size in inches, which can be useful when thinking about breakpoints (for most people, 4.5″ is easier to picture, than 720 logical pixels)

To that end, we have small Screen helper class, that we use across all our new projects:

Continue reading →

Google, Adobe, gskinner | Flutter Interact ’19

Flutter is a mobile UI toolkit that combines fast development, expressive and beautiful UIs, with native performance. To test-drive the platform, Grant Skinner & Mike Chambers recently built Redrix: a mobile companion app for Destiny 2.

Download Redrix on iOS or Android
Continue reading →