Jekyll2025-10-30T18:52:04+00:00https://iiro.dev/feed.xmliiro.devDart and Flutter tutorials, articles, tips and tricks. Sometimes other stuff too. Iiro KrankkaQuickie: AdaptiveImageProvider2021-10-05T00:00:00+00:002021-10-05T00:00:00+00:00https://iiro.dev/adaptive-imagesHere’s a little snippet that I made a couple of years ago that seems to be following me from a Flutter project to another.

It’s a little class that I call AdaptiveImageProvider:


import 'package:flutter/painting.dart';

class AdaptiveImageProvider extends ImageProvider {
  AdaptiveImageProvider(String url) : _delegate = _resolve(url);
  final ImageProvider _delegate;

  static ImageProvider _resolve(String url) {
    final uri = Uri.parse(url);
    switch (uri.scheme) {
      case 'asset':
        final path = uri.toString().replaceFirst('asset://', '');
        return AssetImage(path);
      case 'file':
        final file = File.fromUri(uri);
        return FileImage(file);
      case 'http':
      case 'https':
        return NetworkImage(url);
      default:
        throw ArgumentError('Unsupported scheme: ${uri.scheme}');
    }
  }

  @override
  ImageStreamCompleter load(Object key, DecoderCallback decode) =>
      _delegate.load(key, decode);

  @override
  Future<Object> obtainKey(ImageConfiguration configuration) =>
      _delegate.obtainKey(configuration);
}

You can give this class pretty much any URL and it will poop out an ImageProvider that knows how to display an image using that URL.

“That’s cool, I guess. But why?”

Let’s imagine we have a widget called Avatar. We give it a url that points to an image, and it will be displayed in the shape of a circle:

class Avatar extends StatelessWidget {
  const Avatar({required this.url});
  final String url;

  @override
  Widget build(BuildContext context) {
    return ClipOval(
      child: Image(
        image: AdaptiveImageProvider(url),
        width: 56,
        height: 56,
        fit: BoxFit.cover,
      ),
    );
  }
}

In our app, users can upload their own pictures to set as their avatars.

As such, there are three possible URLs that the Avatar widget has to handle:

  1. user hasn’t uploaded their avatar image - use a preloaded image, e.g. assets://images/person.png
  2. the user is offline - display a locally cached image, e.g. file://path/to/image.png
  3. someone is viewing the users’ profile for the first time - display an image from the web, e.g. https://example.com/images/abc123.png

The AdaptiveImageProvider class makes this easy - just provide a url and it will figure out how to display it. And since it’s extending from the ImageProvider class that comes from Flutter, you can use it with popular libraries, such as cached_network_image and others.

Other use cases

Although the above use cases are the more common ones, the list of possible applications doesn’t end there.

Here are some more:

  • making an in-memory mode of your app where nothing goes to server, where server sync is a premium feature
  • running your app in a “lorem ipsum” mode with placeholder asset images to automate taking app store screenshots in multiple languages
  • creating a “retail demo” version of your app for Apple, so that they can showcase it in Apple Stores (this actually happened at my previous workplace)

I’m sure there are more possible applications, but here are the ones from the top of my head.

]]>
Iiro Krankka
Flutter’s setState() might not be what you think it is2021-03-29T00:00:00+00:002021-03-29T00:00:00+00:00https://iiro.dev/set-stateHere’s a somewhat embarrassing assumption I made about setState when I started to learn Flutter almost 4 years ago.

We all know setState from the counter example:

class _MyWidgetState extends State<MyWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter = _counter + 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text("The current value is $_counter!");
  }
}

Here’s how I thought it worked:

  1. There’s one stateful field called _counter.
  2. We also have a Text widget that displays the value of _counter.
  3. Every time we want to update _counter, we also have to wrap it in an anonymous function and pass it to setState. Otherwise, the framework doesn’t know what was updated.

Four months into my Flutter journey, I found out that assumption number three was not true.

We do have to call setState when updating _counter, but there’s absolutely no need to do it in an anonymous callback.

This:

setState(() {
  _counter = _counter + 1;
});

is exactly the same as this:

_counter = _counter + 1;
setState(() {});

The Flutter framework does not magically inspect code inside the anonymous function and then do some diffing to see what changed.

Whenever you call setState, the widget rebuilds - even if nothing changed and the callback passed to setState was empty.

What I had initially assumed about setState was totally made up in my own head.

A peek behind the curtains

Let’s see how the code behind setState looks like:

@protected
void setState(VoidCallback fn) {
  assert(fn != null);
  assert(() {
    if (_debugLifecycleState == _StateLifecycle.defunct) {
      throw FlutterError.fromParts([/* ... */]);
    }
    if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
      throw FlutterError.fromParts([/* ... */]);
    }
    return true;
  }());
  final dynamic result = fn() as dynamic;
  assert(() {
    if (result is Future) {
      throw FlutterError.fromParts([/* ... */]);
    }
    return true;
  }());
  _element.markNeedsBuild();
}

Let’s remove the assertions:

@protected
void setState(VoidCallback fn) {
  final dynamic result = fn() as dynamic;
  _element.markNeedsBuild();
}

We can actually boil it down even more:

@protected
void setState(VoidCallback fn) {
  fn();
  _element.markNeedsBuild();
}

All that setState does is calling the provided callback and then the associated element gets scheduled for a new build.

No magical field diffing here.

So why do we have setState then?

Because of UX studies ran by the Flutter team.

They do a lot of it.

Back in 2017 and 2018, there was a GitHub issue label called “first hour”. The Flutter team ran developer UX studies and observed how Flutter newcomers would use Flutter when left on their own for an hour. Those studies would shape future API decisions for Flutter.

Apparently, the setState thing is one of these findings.

From the GitHub issue that made me realize I had been living in a lie:

We used to just have a markNeedsBuild method but we found people called it like a good luck charm – any time they weren’t sure if they needed to call it, they’d call it.

We changed to a method that takes a (synchronously-invoked) callback, and suddenly people had much less trouble with it.

There’s also a StackOverflow answer from Collin Jackson from the Flutter team back then:

When Flutter had a “markNeedsBuild” function, developers ended up just sort of calling it at random times. When the syntax switched to setState((){ }), developers were much more likely to use the API correctly.

So the whole setState API is just a mental trick. And it seems to work - apparently it lead into people building their widgets less often.

I never had problems knowing when to call setState, but I can definitely see myself wondering “who’s Mark and what is he building?”

Mystery solved.

Conclusion

What does all of this mean?

Should we now convert all our setState calls to something like this?

_counter = _counter + 1;
setState(() {});

Probably not.

Even though there’s no difference, calling setState with an anonymous callback feels right.

At this point, the anonymous callback is an established convention and anything else just feels weird.

]]>
Iiro Krankka
Parsing and editing HTML in Dart - the right way™2020-11-29T00:00:00+00:002020-11-29T00:00:00+00:00https://iiro.dev/parsing-html-in-dartDuring the last few months, I’ve been quietly building yet another side project.

In one component of that side project I needed to take in some HTML, change some things, and return slightly different HTML.

“I’ll just slap together some incomprehensible regex… and voilà! That wasn’t hard at all!”

If you’ve ever gone on StackOverflow to find, or god forbid, ask for the one HTML regex to rule them all, somebody will tell you quite quickly that it isn’t such a great idea.

Since HTML is far from a regular language, parsing it with regular expressions can be full of weird edge cases. The nice thing is that proper HTML parsers handle the edge cases for you.

As an added benefit, using a proper HTML parser is also simpler than hacking together some messy regular expressions.

The Hello World

Given Dart’s history, it has a quite solid toolbox for working with all things Javascript, the DOM, CSS and, you guessed it, parsing HTML.

To get started, all we need to do is to add package:html to our pubspec file:

dependencies:
  html: # check pub.dev for latest version

It’s time to parse some good old HTML.

import 'package:html/parser.dart' as html_parser;

void main() {
  final document = html_parser.parseFragment('''
    <p><a href="https://dart.dev">Dart</a></p>
    <p><a href="https://flutter.dev">Flutter</a></p>
  ''');

  final anchors = document.querySelectorAll('a');

  for (final anchor in anchors) {
    final href = anchor.attributes['href'];
    print('${anchor.text} - $href');
  }
}

That would print out the following:

Dart - https://dart.dev
Flutter - https://flutter.dev

Since we’re parsing some HTML, but not a whole web page, we used parseFragment. If we were parsing a whole webpage, we’d use the parse method instead.

The parseFragment method gives us a DOM tree that kinda looks like this:


The resulting DOM tree from parsing the HTML text input.

The resulting DOM tree from parsing the HTML text input.

For simple cases, we can just use the .querySelector() method on the document. This works when we want to find all the <a> elements on a page or something equally simple.

But if we’re building something more complex, like a pretty printer for HTML code, chances are we need to iterate over the list of nodes, and then use some recursion in order to process every single nested element. We’ll have examples of this later.

Displaying top 10 Google search results

Let’s imagine we want to get the top 10 Google search results for the query flutter blogs and print them to the console.

Easy-peasy.

import 'package:html/parser.dart' as html_parser;
import 'package:http/http.dart' as http;

void main() async {
  // Fetch Google search results for "flutter blogs".
  final response = await http
      .get(Uri.parse('https://www.google.com/search?q=flutter+blogs'));
  final document = html_parser.parse(response.body);

  // Google has the best class names.
  final results = document.getElementsByClassName('BNeawe vvjwJb AP7Wnd');

  print('Top 10 results for "flutter blogs":\n\n');

  var placement = 1;
  for (final result in results) {
    print('#$placement: ${result.text}');
    placement++;
  }
}

That should print this:

Top 10 results for "flutter blogs":

#1: Flutter – Medium
#2: Flutter Blog by GeekyAnts – The GeekyAnts Blog
#3: Flutter Blogs (@FlutterBlogs) | Twitter
#4: Blogs Archive - FlutterDevs - Why Flutter?
#5: Guest Blog: Flutter Trends and Community Updates | Syncfusion Blogs
#6: The top 45 must-follow Flutter experts on Twitter - Codemagic blog
#7: iiro.dev | Dart and Flutter tutorials, articles, tips and tricks ...
#8: Flutter Blog Engine | ButterCMS
#9: Top Apps Made with Flutter – 17 Stories by Developers and ...
#10: Flutter vs React Native: A Developer's Perspective - Nevercode

I don’t know about the other ones, but #7 on that list is total garbage.

I’m not placing that high on search results anymore - I guess I should write more articles.

Editing the parsed DOM tree

As the DOM tree is a nested structure of Dart objects, we can also easily modify it on the fly.

Let’s imagine we’re building a comment system in Dart.

Among other things, such as disallowing malicious HTML and sanitizing other content, we probably want to handle the links.

We’ll want to:

  1. open all links in a new window
  2. tell search engines (such as Google) to not give these links credit in search engine rankings.

The way to do it is to add rel="external nofollow" and target="_blank" to each <a> element.

This is one way to do it:

import 'package:html/parser.dart' as html_parser;

void main() {
  final document = html_parser.parseFragment('''
    <p>Nice blog post! I also wrote about the same thing on my blog,
    <a href="https://example.com">check it out</a>!</p>
  ''');

  // Find all anchor ("<a>") elements in the parsed document.
  final anchors = document.querySelectorAll('a');

  for (final anchor in anchors) {
    // Iterate over all <a> elements and add these attributes
    // to each one.
    anchor.attributes.addAll({
      'target': '_blank',
      'rel': 'nofollow',
    });
  }

  print(document.outerHtml);
}

That should print the following:

<p>Nice blog post! I also wrote about the same thing on my blog,
<a href="proxy.php?url=https://example.com" target="_blank" rel="nofollow">check it out</a>!</p>

Allowing only specific HTML tags

Remember how I told there are two ways to process the DOM tree, the simple way and the less simple way?

It’s time to learn the less simple way. But there’s no reason to worry - it’s not too hard!

Maybe we’re building a comment form, and only want to allow bold, italics, and underline text?

Easy peasy:

import 'dart:convert';

import 'package:html/dom.dart';
import 'package:html/parser.dart' as html_parser;
import 'package:meta/meta.dart';

final _htmlEscape = HtmlEscape(HtmlEscapeMode.element);

class LimitedHtml {
  LimitedHtml({required this.allowedTagNames});
  final Set<String> allowedTagNames;

  String filter(String html) {
    final buffer = StringBuffer();
    final document = html_parser.parseFragment(html);
    _visitNodes(buffer, document.nodes);

    return buffer.toString().trim();
  }

  void _visitNodes(StringSink sink, List<Node> nodes) {
    for (final node in nodes) {
      if (node is Element) {
        _visitElement(sink, node);
      } else if (node is Text) {
        sink.write(_htmlEscape.convert(node.text));
      }
    }
  }

  void _visitElement(StringSink sink, Element element) {
    final tag = element.localName!.toLowerCase();

    if (allowedTagNames.contains(tag)) {
      sink.write('<$tag>');
      _visitNodes(sink, element.nodes);
      sink.write('</$tag>');
    } else {
      sink.write(element.text);
    }
  }
}

In just roughly 30 lines, we built a reusable class that takes in a list of allowedTagNames and ignores everything else.

Let’s try it in action:

void main() {
  final html = '''
   <p>I can't <u>underline</u> enough how <b>bold</b> and <i>italic</i> night it was!</p>
   <div>
    <a href="javascript:doSomethingBad()">Click me please!</a>
   </div>
  ''';

  final limitedHtml = LimitedHtml(allowedTagNames: {'p', 'b', 'i', 'u'});
  print(limitedHtml.filter(html));
}

That would print the following:

<p>I can't <u>underline</u> enough how <b>bold</b> and <i>italic</i> night it was!</p>
   
Click me please!

Because the DOM tree can in theory nest infinitely, we have to use some recursion. It’s always a bit of weird to wrap your head around it, but thankfully this is one not the worst kind to understand.

We’re also using a StringBuffer. You can think of it as an efficient way for combining a lot of strings into one string.

In the case of a small amount of strings, it’s not needed. But if we don’t know how many strings we need to combine and there might be a lot of them, StringBuffer is a good choice.

Converting HTML to Markdown

There are a lot of Markdown to HTML converters, but what about going the other way?

import 'dart:convert';

import 'package:html/dom.dart';
import 'package:html/parser.dart' as html_parser;

final _htmlEscape = HtmlEscape(HtmlEscapeMode.element);

class HtmlToMarkdown {
  String convert(String html) {
    final buffer = StringBuffer();
    final document = html_parser.parseFragment(html);
    _visitNodes(buffer, document.nodes);

    return buffer.toString().trim();
  }

  void _visitNodes(StringSink sink, List<Node> nodes) {
    for (final node in nodes) {
      if (node is Element) {
        _visitElement(sink, node);
      } else if (node is Text) {
        sink.write(_htmlEscape.convert(node.text.trim()));
      }
    }
  }

  void _visitElement(StringSink sink, Element element) {
    final tag = element.localName!.toLowerCase();

    switch (tag) {
      case 'p':
        sink.writeln();
        sink.writeln();
        _visitNodes(sink, element.nodes);
        break;
      case 'strong':
        sink.write(' **');
        _visitNodes(sink, element.nodes);
        sink.write('** ');
        break;
      case 'em':
        sink.write(' _');
        _visitNodes(sink, element.nodes);
        sink.write('_ ');
        break;
      default:
        _visitNodes(sink, element.nodes);
        break;
    }
  }
}

It’s far from complete, but it’s good enough for a limited sample.

Let’s try it. This:

void main() {
  final html = '''
   <p>Hello <strong>there</strong> world!</p>
   <p>Have a <em>lovely</em> day!</p>
  ''';

  print(HtmlToMarkdown().convert(html));
}

prints the following:

Hello **there** world!

It's quite a _lovely_ day!

Not to bad considering it’s just 50 lines of code.

There is an infinite amount of possible uses for parsing HTML in Dart. Somebody even built a package that converts HTML to Flutter widgets.

What will you build?

]]>
Iiro Krankka
Controlling time in Dart unit tests, the better way2020-11-14T00:00:00+00:002020-11-14T00:00:00+00:00https://iiro.dev/controlling-time-with-package-clockIn Dart, we can get the current date and time by calling DateTime.now().

That’s easy enough. But what’s not that easy is testing it. Since there’s no built-in way to override what it returns, any Dart code that directly depends on DateTime.now() is not testable.

However, there’s a very neat way to tackle this, and it’s not what you might think.

Why testing DateTime.now() is hard

Let’s imagine that we’re making an app that tells the current day of the week.

We have a class called Greeter that returns the appropriate greeting for each day.

greeter.dart
import 'package:intl/intl.dart';

final _format = DateFormat('EEEE');

class Greeter {
  String greet() {
    final now = DateTime.now();
    final dayOfWeek = _format.format(now);
    return 'Happy $dayOfWeek, you!';
  }
}

For example, if it’s Tuesday, Greeter().greet() returns “Happy Tuesday, you!”.

Writing some failing tests

We want to be sure nobody introduces bugs to the very important greeting functionality, so we write a test case:

greeter_test.dart
void main() {
  test('returns a proper greeting on a Tuesday', () {
    final greeting = Greeter().greet();

    // How on Earth do we make this expectation pass
    // if it's not Tuesday when we run this test?
    expect(
      greeting,
      'Happy Tuesday, you!',
    );
  });
}

The problem with this test is that it passes only once a week.

Unless our team agrees to only run tests on Tuesdays, that test case is not very useful. To make it pass every day of the week, we need to be able to control the current time somehow.

Making it testable - the usual way

We could refactor Greeter to something like this:

greeter.dart
DateTime _getSystemTime() => DateTime.now();

class Greeter {
  Greeter([this._getCurrentTime = _getSystemTime]);
  final DateTime Function() _getCurrentTime;

  String greet() {
    final now = _getCurrentTime();
    final dayOfWeek = _format.format(now);
    return 'Happy $dayOfWeek, you!';
  }
}

The class now has an optional constructor parameter: a function called _getCurrentTime().

By default, it returns the current time, but the behavior can be overridden. In our tests, we pass Greeter a function that returns a fixed DateTime instead.

greeter_test.dart
void main() {
  test('returns a proper greeting on a Tuesday', () {
    final greeting = Greeter(() => DateTime(2020, 09, 01)).greet();
    expect(greeting, 'Happy Tuesday, you!');
  });
}

With these changes, our tests now pass 7 days a week!

The end?

That approach works for the very simple use case we had, but it’s not a fun thing to do at scale.

We would need to do this dance with every single class or widget that needs the current datetime. This inevitably results in “prop drilling” when dealing with widgets and other nested classes. That’s not fun.

Well, what about InheritedWidget - or better yet, provider?

We could come up with a class called Clock that encapsulates the behavior:

DateTime _defaultTime() => DateTime.now();

class Clock {
  Clock([this.now = _defaultTime]);
  final DateTime Function() now;
}

We could then pass Clock down with an InheritedWidget or provider to make it available for a subtree.

This would avoid the problem of prop drilling and still keep the logic testable. However, we wouldn’t be able to access current time without a BuildContext. It would also complicate accessing the Clock in initState(), since context is not available there yet.

Also, if we have a lot of Dart classes depending on the current datetime that are not Flutter widgets, we’d still have to pass the Clock as a constructor argument over and over again.

There’s got to be a better way!

What about just making Clock a singleton?

That would work.

I did it years back myself with inKino. You can see the source code for Clock here, and here’s an example test case, although it’s a bit specific to Redux.

However, I can’t help but feel a bit off with this one.

It’s global behavior that can be overridden from anywhere. This could result in frustrating, hard to notice bugs.

The better way - controlling time with package:clock

Even though there’s no built-in way to mock DateTime.now(), there’s a good alternative that comes pretty close.

It’s a package called clock, and it’s maintained by the Dart team.

The package exposes a top-level variable called clock. To get the current time, we call clock.now():

import 'package:clock/clock.dart';

void main() {
  // prints current date and time
  print(clock.now());
}

In order to override the current time, we wrap any code that calls clock.now() with a function called withClock:

import 'package:clock/clock.dart';

void main() {
  withClock(
    Clock.fixed(DateTime(2000)),
    () {
      // always prints 2000-01-01 00:00:00.
      print(clock.now());
    },
  );
}

The withClock function takes in two arguments:

  1. a Clock - in our case, a clock with a fixed date set to 2000/01/01.
  2. a function, inside of which the clock getter always returns what we passed in as the first argument.

That’s pretty neat.

It also only requires minimal changes to existing code. And there’s no need to add yet another constructor parameter and pass stuff around.

How does it work?

Short answer: it leverages Zone-local values.

Here’s how the withClock function looks like:

class Clock {
  // ...
}

// An unique object to use as a key for the clock.
final _clockKey = Object();

T withClock<T>(Clock clock, T Function() callback) {
  return runZoned(
    callback,
    zoneValues: {
      // Override _clockKey with the provided clock.
      _clockKey: clock,
    },
  );
}

It runs the passed anonymous function in a new Zone, setting a Zone-local value for the _clockKey to contain the provided clock.

Any code inside callback that queries _clockKey from the current Zone will receive an overridden Clock.

You might see where this is going.

// Look up if the current Zone has a clock assigned, and
// return that. If not, return a fresh new instance of Clock.
Clock get clock => Zone.current[_clockKey] as Clock? ?? Clock();

The top-level clock getter checks for the same _clockKey in the current Zone.

If a Clock already exists in the current Zone (=we have wrapped that piece of code with the withClock function), it returns the provided Clock.

Otherwise, it returns a fresh instance of Clock that defaults to returning DateTime.now().

Fixing our tests

Now that we have this brand new trick up our sleeves, we’ll finally make our sample test case work properly.

Let’s implement this in our Greeter class. To make it work, we only need to make one tiny modification to greeter.dart:

greeter.dart
import 'package:clock/clock.dart';

class Greeter {
  String greet() {
    // ...
    final now = clock.now();
    // ...
  }
}

After that, we wrap our tests with the withClock() function:

greeter_test.dart
void main() {
  test('returns a proper greeting on a Tuesday', () {
    final greeting = withClock(
      Clock.fixed(DateTime(2020, 09, 01)),
      () => Greeter().greet(),
    );

    expect(greeting, 'Happy Tuesday, you!');
  });
}

Now the test passes on every day of the week - not just Tuesday!

Other use cases

Aside from overriding current time in just unit tests, depending on your app, you might benefit from this in your manual testing too.

For example, in Reflectly, we have some things that should be only loaded once a day. In order to test this manually, I just wrap runApp with withClock that starts with the current date.

Then I bump the day by one to simulate tomorrow, the day after, and so on.

]]>
Iiro Krankka
When in doubt, just use Provider2020-07-12T00:00:00+00:002020-07-12T00:00:00+00:00https://iiro.dev/just-use-providerThere still seems to be a lot of confusion about what’s “the best” state management library to use with Flutter.

Disappointingly, the right answer to these types of questions is always somewhere along the lines of “there’s no single best library - it all depends”.

“It depends?! Well, gee, thanks a lot for nothing! I guess I’ll try all the state management libraries in existence and figure out the best one myself. See you again around the time when my teeth fall out and I have dentures.”

Well, what does “the best state management library” mean to you?

If you’re looking for something that:

  1. scales when your app scales, and
  2. doesn’t make you want to create voodoo dolls out of the person who made the library and stab them in their ears with chopsticks

then fair enough.

You should know that most of the available state management libraries fit that criteria.

But you wouldn’t be on Reddit complaining that “there’s too many Flutter state management libraries!1!! I want fewer options!1!1” if this was your criteria.

In reality, you’re looking for “something that the Flutter team and the community as a whole 100% agree is the best library now and forever and that everybody will be forced to use until the heat death of the universe”.

Spoiler alert: there is no such thing. And, likely, there will never be such a thing.

“But why does it have to be popular?”, you ask.

“For two reasons”, I answer:

  1. The more popular it is, the more learning resources and examples it will have.
  2. It’s more likely that your colleagues are already somewhat familiar with it. Which means you can focus on building on whatever it is that you’re building.

And when I say “popular”, I don’t mean “the hot new thing that just appeared yesterday that everybody is talking about”.

You also want something that has been around for a while.

“So, what are some examples of popular libraries for state management?”

In no particular order, some of the most established options would be scoped_model, redux, mobx, bloc, and provider.

If you ask me, anything out of these usual suspects is a bit on the hit-or-miss territory.

“Okay, but that’s still five libraries, and I still don’t know which one to choose.”

That’s why we have the WIDJUP rule.

When In Doubt, Just Use Provider.

Provider is all you need for state management

If you’re overwhelmed by all the state management libraries and don’t know which one to choose, Just Use Provider™.

Provider has everything for your state management needs, both today and tomorrow.

Don’t get stuck in analysis paralysis; call 1-800-PROVIDER and get your very own Provider today!

“But Provider is not state management! It’s just a better InheritedWidget.”

You know what I mean.

Just like you know what your friend means when he tells you that he’s eating 8000 calories every day to prepare for a sumo wrestling competition. Yes, he means kilocalories, not calories. Everybody knows that.

But fine.

pedantic mode off

Turn on the switch. I hope that it makes you happy.

“Very funny.”

Thanks.

“Whatever. So, Provider is all I need? Isn’t that quite a bold statement?”

Not at all. Here’s a bold statement for you:

A bold statement.

“Sigh. What I meant is that can I use Provider in serious applications?”

Yes, just like you can use Provider in silly applications.

Such as in a fart soundboard app.

“Can you just stop with the dad jokes and write your articles like a normal person?”

*turns on professional mode*

Provider is mature enough

Provider is the most popular state management library on Pub.

It’s also probably the most liked library on Pub at 1.5k likes and counting. I say “probably”, because Pub doesn’t allow sorting by likes.

Aside from being popular, Provider has also existed for more than a year at this point. Having gone through several major version changes, the API should’ve stabilized enough by now.

Being popular, but not brand-spanking-new at the same time, is a good thing.

For what it’s worth, Provider is also recommended by Google and it has been recommended for over a year now.

It’s like scoped_model

Do you remember how back in the good old days, there were pretty much two viable state management libraries?

No? Well, Pepperidge Farm and a small group of early adopters remember.

Those two options were scoped_model and flutter_redux. Both packages have existed since August 2017.

“What does this have to do with Provider?”

I’ll show you.

Here’s one way to use scoped_model:

class Counter extends Model {
  int _counter = 0;
  int get counter => _counter;
  set counter(int value) {
    _counter = value;
    notifyListeners();
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // This assumes that there's ScopedModel<Counter>
    // widget somewhere in the widget tree and
    // it wraps "MyWidget".
    return ScopedModelDescendant<Counter>(
      builder: (_, __, model) => Text('${model.counter}'),
    );
  }
}

Here’s the same example, but with Provider:

class Counter extends ChangeNotifier {
  // The rest of it is the same.
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Assumes a ChangeNotifierProvider<Counter> above
    // in the widget tree.
    return Consumer<Counter>(
      // The rest of it is the same.
    );
  }
}

See the difference?

  1. with Provider, we extend ChangeNotifier instead of Model
  2. we pass the model around with ChangeNotifierProvider instead of ScopedModel
  3. we access the model with Consumer instead of ScopedModelDescendant

It’s the same thing, but with different names. There’s a guy from the ’80s with long hair who says it better than I ever could.

Okay, so scoped_model has existed for three years. And scoped_model and Provider are very similar. So what?

Well, it proves that the approach from scoped_model has stood the test of time. Three years and counting. It works.

It’s maybe even more than three years if you account for the fact that Fuchsia has been using the same approach internally before scoped_model extracted it to a package.

Provider is simple

While provider has more advanced classes such as ProxyProviders, you don’t have to use them.

All you need to get started is to know the following:

  1. use ChangeNotifiers to hold your state
  2. provide a ChangeNotifier to a subtree with ChangeNotifierProvider
  3. obtain a ChangeNotifier from ancestor widgets and rebuild automatically with Consumer.

There’s a good tutorial on the Flutter website on how to get started.

Once you become more comfortable with the concepts, you might want to take a look at Selector, which helps filter the data that triggers rebuilds to your widgets. But that’s really it.

Provider is scalable

Like many other state management libraries, Provider scales when your app scales.

But just like with scoped_model, it’s possible to shoot yourself in the foot by doing something weird.

Local state > global state

When I see people complaining about scoped_model or provider not being scalable, this is almost always what they are doing:

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider<MyNotifier1>(/* ... */),
        ChangeNotifierProvider<MyNotifier2>(/* ... */),
        ChangeNotifierProvider<MyNotifier3>(/* ... */),

        // * a few ChangeNotifierProviders later *
        ChangeNotifierProvider<MyNotifier99>(/* ... */),
      ],
      child: MaterialApp(/* ... */),
    ),
  );
}

In this case, instead of wrapping individual routes with their equivalent ChangeNotifiers, someone decided to make everything sit above the whole app.

This lifts local state into heights where it doesn’t have to go to.

There’s suddenly no boundaries on who accesses what. This, in turn, makes it impossible to dispose of unneeded ChangeNotifiers when users navigate away from their relevant routes.

Sharing a ChangeNotifier between many distinct routes also tends to result in very bloated ChangeNotifiers. This is not fun when your app starts having a lot of routes.

If you keep your state local, there’s no real reason why Provider wouldn’t scale with your app.

You’re probably already depending on provider

Given the popularity of provider, chances are that you already have it in your pubspec file. If not, you might be depending on a library that uses it internally.

And if you’re already depending on it, why not just go ahead and use the same thing to manage state too?

But what about clean architecture?

It’s a good idea to separate business logic from the UI framework.

But it’s also a good idea to be pragmatic about it.

Most apps don’t have to go all-in with clean architecture just because that’s how you get the most internet points from your enterprise Java friends.

If you’re a single developer hoping to build a successful app, you don’t want to get stuck wondering how to plumb Interactors together with Presenters, Services, Repositories, and then write the mappers that convert them in and out of domain models.

What you want to do instead is to build the MVP and ship it.

But if it’s a hobby app, and your sole purpose is to learn how clean architecture works, then it’s a very different story. In that case, the answer is “manage state with something that doesn’t have Flutter dependencies”.

But what about <insert new shiny state management library here>?

Do you remember the advice at the very beginning?

Pick a popular library and stick with it.

Think about this:

You could’ve been using scoped_model since 2017, ignoring all the other state management libraries in between (even Provider), and you would be fine today. Yeah, really.

A new state management library coming out today doesn’t suddenly make your existing code rotten.

Stop chasing shiny things

As an example, look at Invoice Ninja.

Out of scoped_model and flutter_redux, Invoice Ninja chose flutter_redux. And they stuck with it.

They released their first version back in the summer of 2018. Even back then, a large portion of the Flutter developer community didn’t really like Redux. But that didn’t matter.

Redux worked fine for Invoice Ninja, and that was the only thing that mattered. They still use Redux to this day.

Sure, Redux is boilerplatey, and it has its flaws, but it doesn’t matter. The virtue of using the newest and hippest community-approved libraries doesn’t necessarily make your business succeed.

Just build it already

You know what makes your business succeed?

Shipping useful apps does.

Invoice Ninja didn’t keep chasing the coolest state management library of the week. They didn’t complain about there being “too many state management libraries”. And they still don’t.

Invoice Ninja picked a state management library, stuck to it, and focused on their business instead of worrying if people think that Redux is cool enough.

Want to hear a secret?

You can do it too.

But instead of picking a library and starting to build apps with it, you sit on your hands and complain on Reddit that there are too many state management libraries.

]]>
Iiro Krankka
Restricting system textScaleFactor, when you have to2020-05-18T00:00:00+00:002020-05-18T00:00:00+00:00https://iiro.dev/restricting-system-text-scale-factornote: I received feedback pointing out that if the user chooses a huge font, they need it, and you should let them. I agree. However, sometimes it’s not up to you to decide. Or maybe something else is a bigger priority right now. In those situations, you need to find a compromise. Anything is better than locking textScaleFactor to a hardcoded value. This article is written that scenario in mind.

Up until very recently, we were setting the textScaleFactor manually to 1.0 on every Text widget in Reflectly like this:

@override
Widget build(BuildContext context) {
  return Column(
    children: [
      Text(
        'something very important',
        textScaleFactor: 1.0,
      ),
      Text(
        'something equally important',
        textScaleFactor: 1.0,
      ),
    ],
  );
}

What initially to me seemed like a very weird thing to do (it defaults to 1.0 - why set it explicitly?), proved to have some reasoning behind it.

It was there to ignore the system-wide text size settings. The textScaleFactor, if unset, defaults to the value from the nearest MediaQuery.

The value for the MediaQuery, if not overridden, comes from device settings.


Screenshot of iOS accessibility settings with text size set to maximum.

Screenshot of iOS accessibility settings with text size set to maximum.

The textScaleFactor that comes from the device settings can be anywhere from 1.0 to some very large values.

If we go to the accessibility settings of our iDevice and choose the largest possible text size, Reflectly becomes quite unflattering:


On the left, the main timeline page of Reflectly with normal text size.
 On the right, the same page with text size set to maximum settings.

On the left, the main timeline page of Reflectly with normal text size. On the right, the same page with text size set to maximum settings.

Not very pretty, is it?

The hardcoded textScaleFactors for every single Text widget made at least some sense now.

Providing a default textScaleFactor for a subtree

Setting textScaleFactor manually for every single Text widget is not a very scalable solution.

If we forget just one Text widget, it will stand out and make our UI look inconsistent.

The good news is that we can override textScaleFactor for an entire subtree:

@override
Widget build(BuildContext context) {
  return WidgetsApp(
    // ...
    builder: (context, child) {
      // Obtain the current media query information.
      final mediaQueryData = MediaQuery.of(context);

      return MediaQuery(
        // Set the default textScaleFactor to 1.0 for
        // the whole subtree.
        data: mediaQueryData.copyWith(textScaleFactor: 1.0),
        child: child!,
      );
    },
  );
}

note: Although this entire article talks about WidgetsApp, it works exactly same with MaterialApp and CupertinoApp.

Now we don’t need to provide textScaleFactor to our Text widgets anymore.

It means that this:

@override
Widget build(BuildContext context) {
  return Text(
    'Hello world!',
    textScaleFactor: 1.0,
  );
}

is exactly same as this:

@override
Widget build(BuildContext context) {
  return Text('Hello world!');
}

No matter what the device text size is, our Text widgets will always ignore it and act as if the factor was always 1.0.

Yay for ignoring a user preference without having to repeat repeat ourselves ourselves!

How it works

Let’s refer to a piece in textScaleFactor documentation for the Text widget:

If null, will use the MediaQueryData.textScaleFactor obtained from the ambient MediaQuery.

We’re making the nearest MediaQuery be the one where we set a default text scale factor.


A diagram that shows the rough difference between before and after, with some oomph to make the change more prominent.

A diagram that shows the rough difference between before and after, with some oomph to make the change more prominent.

Whenever a widget calls MediaQuery.of(context), it will get our MediaQuery with the overridden textScaleFactor.

And that is what the Text widget will use when resolving the default textScaleFactor.

Making it more adaptive

Setting the textScaleFactor to 1.0 and calling it a day means ignoring your users needs. They’re asking for a bigger text size and you’re not delivering it.

On the other side of the argument you might have your designer or client. They don’t want the app to look ugly.

Or maybe some other feature/bug is more important right now, and you’ll fix this later.

Like with anything else in life, there’s a middle ground solution that pleases both:

@override
Widget build(BuildContext context) {
  return WidgetsApp(
    // ...
    builder: (context, child) {
      // Obtain the current media query information.
      final mediaQueryData = MediaQuery.of(context);

      // Take the textScaleFactor from system and make
      // sure that it's no less than 1.0, but no more
      // than 1.5.
      final num constrainedTextScaleFactor =
          mediaQueryData.textScaleFactor.clamp(1.0, 1.5);

      return MediaQuery(
        data: mediaQueryData.copyWith(
          textScaleFactor: constrainedTextScaleFactor as double?,
        ),
        child: child!,
      );
    },
  );
}

Instead of a hardcoded textScaleFactor, this time we’re providing a constrained system value.

By using clamp(), we’re giving the system textScaleFactor a lower and upper bound.

This allows the users to control the text size without going completely overboard.

Writing tests for it

“There’s nothing to test! It’s just a couple lines of code!”

I thought so too, until we released this thing and Crashlytics started reporting about crashes in production.

Without going into details, believe me when I say it was a stupid mistake.

I fixed the bug with accompanying test cases that make sure it doesn’t happen again. Here’s roughly how it works.

Extracting the behavior to a widget

We don’t care about other stuff in our main.dart - we only want to test the text scale factor logic.

The easiest way to do this is to extract the logic into a separate widget, and write tests for that widget.

We’ll call it TextScaleFactorClamper:


class TextScaleFactorClamper extends StatelessWidget {
  const TextScaleFactorClamper({required this.child});
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final mediaQueryData = MediaQuery.of(context);
    final num constrainedTextScaleFactor =
        mediaQueryData.textScaleFactor.clamp(1.0, 1.5);

    return MediaQuery(
      data: mediaQueryData.copyWith(
        textScaleFactor: constrainedTextScaleFactor as double?,
      ),
      child: child,
    );
  }
}

Now the logic is nicely contained inside TextScaleFactorClamper.

It takes the system textScaleFactor, makes sure it’s between 1.0 - 1.5, and passes it down as the default value for the subtree.

Testing it

Now that the logic has been extracted, testing it in isolation becomes quite easy:

import 'package:flutter_test/flutter_test.dart';

import 'text_scale_factor_clamper.dart';

void main() {
  group('TextScaleFactorClamper', () {
    double? effectiveTextScaleFactor;

    setUp(() {
      effectiveTextScaleFactor = null;
    });

    Future<void> pumpWithTextScaleFactor(WidgetTester tester, double factor) {
      return tester.pumpWidget(
        MediaQuery(
          data: MediaQueryData(textScaleFactor: factor),
          child: TextScaleFactorClamper(
            child: Builder(builder: (context) {
              // Obtain the effective textScaleFactor in this context and assign
              // the value to a variable, so that we can check if it's what we
              // want.
              effectiveTextScaleFactor = MediaQuery.of(context).textScaleFactor;

              // We don't care about what's rendered, so let's just return the
              // most minimal widget we can.
              return const SizedBox.shrink();
            }),
          ),
        ),
      );
    }

    testWidgets('constrains the text scale factor to always be between 1.0-1.5',
        (tester) async {
      await pumpWithTextScaleFactor(tester, 5);
      expect(effectiveTextScaleFactor, 1.5);

      await pumpWithTextScaleFactor(tester, 0.1);
      expect(effectiveTextScaleFactor, 1);

      await pumpWithTextScaleFactor(tester, -5.0);
      expect(effectiveTextScaleFactor, 1);

      await pumpWithTextScaleFactor(tester, 1.25);
      expect(effectiveTextScaleFactor, 1.25);
    });
  });
}

The meat and potatoes is in the pumpWithTextScaleFactor() method.

It uses the Builder widget to obtain the correct context that has our textScaleFactor applied. The effective text scale factor is then assigned to a variable, aptly named effectiveTextScaleFactor.

After that, it’s easy to just pump widgets with varying text scale factors and verify the correctness with expect() statements.

Wrapping up

Now we can be relatively certain our app displays reasonably sized text, and most importantly, doesn’t crash.

Better yet, we’re able to pass the sane default value to an entire subtree. Surely beats manually setting textScaleFactor to 1.0 on every single Text widget!

I guess that’s the lesson for today - if it feels like there has to be a better way to do something, there almost always is.

]]>
Iiro Krankka
Running arbitrary strings as Dart code2019-08-15T00:00:00+00:002019-08-15T00:00:00+00:00https://iiro.dev/how-to-eval-in-dart(NOTE: This only works in JIT mode. It’s a viable solution only if you’re building a Dart app that runs with Dart VM and which isn’t a AOT compiled. You can’t use this approach with Flutter. Or you can, but it stops working in release mode.)

In Javascript, it’s possible to evaluate a string as code:

why-would-you-do-that.js
const code = 'console.log("hey!")';

// Execute "code" as Javascript
eval(code);

Javascript has eval(), which is a special function that takes in a string and evaluates it as Javascript code. The above example prints a friendly “hey!” to the console.

“But Liro, why would you not just call console.log() directly?”

First, my name is Iiro. And to answer the question, you’re absolutely right. You would in fact do just that.

Here’s the same thing but with less code:

much-better.js
console.log("Hey!")

If the result of both examples is the same, why would we ever want to eval() in the first place?

In this case, we wouldn’t. That code snippet is just as an example - not an actual use case. A real use case for using eval() is quite hard to come up with.

So - when do I need to eval()?

Most of the time, you should not use eval() at all. Chances are that you’re never going to need it in application development.

In very rare cases, you might actually need it. Most likely when building developer tools - after all, there’s no REPL without eval.

I found myself needing eval() today when building something in Dart. And guess what, it was in the context of building a developer tool.

final functionName = 'applyMagicSauce';

The functionName here is hacked together during runtime. It’s done by searching for implementers of a specific abstract class in a Dart project.

Those abstract classes are written by me, but the implementations come from third-party libraries. There might be multiple functionNames to call.

I needed to take that functionName and do something like this:

final functionName = 'applyMagicSauce';
final input = 'some arbitrary string';

// Construct a call to "applyMagicSauce()"
// with "some arbitrary string" as input.
final result = eval('$functionName("$input")');

Calling applyMagicSauce(..) and passing it a string returns a slightly different string. I needed to find a way to call it.

Where did you hide your eval(), Dart?

Dart does not have an eval() function. What a bummer.

If you’re running Dart in a web browser, you could kinda do it by calling Javascript from Dart like this:


void main() {
  final result = js.context.callMethod("eval", /* ... */);
}

But that won’t work when running Dart on the VM.

I’m also pretty sure I wouldn’t get anything useful back if I tried to send some Dart to a Javascript eval() function.

After some more investigative Googling, I stumpled upon this comment on a GitHub issue:

You can create a new library with a main function, encode the source code as a data: URI and spawn it as a new isolate, but that will not allow you to create new functions in the current isolate. You’ll have to communicate with the new code using isolate port communication.

I decided to try just that. There was a function called Isolate.spawnUri that seemed to be just what I needed.

It takes in an Uri - which among other things, can be constructed from a string by calling Uri.dataFromString.


void main() async {
  final uri = Uri.dataFromString(
    '''
    void main() {
      print("Hellooooooo from the other side!");
    }
    ''',
    mimeType: 'application/dart',
  );
  await Isolate.spawnUri(uri, [], null);
}

Guess what? The damn thing worked.

ironman$ dart lib/eval.dart

Hellooooooo from the other side!

Yay for executing arbitrary strings as Dart code! What about sending something back?


void main() async {
  final name = 'Eval Knievel';
  final uri = Uri.dataFromString(
    '''
    import "dart:isolate";

    void main(_, SendPort port) {
      port.send("Nice to meet you, $name!");
    }
    ''',
    mimeType: 'application/dart',
  );

  final port = ReceivePort();
  await Isolate.spawnUri(uri, [], port.sendPort);

  final String? response = await (port.first as FutureOr<String?>);
  print(response);
}

(Isolates in Dart are quite… isolated. They don’t share memory. In order to pass data around, we need to use ports - which might seem a little cumbersome. On the other hand, we don’t run into synchronization issues, so there’s that.)

That one worked too:

ironman$ dart lib/eval.dart

Nice to meet you, Eval Knievel!

Remember applyMagicSauce() from earlier? Let’s imagine I have the full package: URI for the containing file in a variable called packageUri. Because I actually do.

I also know that the name of the function I want to call is applyMagicSauce. It takes in one argument which is a String.

It’s time to stitch some strings together.


void main() async {
  // I filled out the content of these variables here
  // for clarity. In a real scenario, you'd probably
  // parse these from somewhere.
  final packageUri = 'package:magic/magic_sauce.dart';
  final functionName = 'applyMagicSauce';
  final input = 'Hellooooooo from the other side!';
  final uri = Uri.dataFromString(
    '''
    import "dart:isolate";
    import '$packageUri';

    void main(_, SendPort port) {
      port.send($functionName("$input"));
    }
    ''',
    mimeType: 'application/dart',
  );

  final port = ReceivePort();
  final isolate =
  await Isolate.spawnUri(uri, [], port.sendPort);
  final String? response = await (port.first as FutureOr<String?>);

  port.close();
  isolate.kill();

  print(response);
}

After running this, the result of applyMagicSauce will be in the response variable.

If the magic sauce function was shuffling a string around, we would get something like this printed on the console:

ironman$ dart lib/eval.dart

lor meetei ooo sdel fthooh!oHoor

Final words

In Javascript, the first rule of using eval is to not use it. It applies for Dart too.

It’s usually hacky and there’s going to be a better way. If you’re just building Flutter apps, you’re probably never going to need it. In fact, if you’re building a Flutter app, this approach will not work for you.

The second rule is that eval can be evil. If you do run it with Dart VM, you need to seriously think about making sure that you’re not accidentally running something malicious on your end user’s computer.

]]>
Iiro Krankka
From mobile to web in less than 4 beers2019-05-15T00:00:00+00:002019-05-15T00:00:00+00:00https://iiro.dev/reflectly-mobile-to-web-with-flutterWhen I joined Reflectly eight months ago, I was tasked to recreate a subset of our Flutter app — but for the web.

Our initial goal was to implement basic account functionality with support for payments and discount codes. This would improve the experience for new users signing up via referral links. The long-term plan was to have a 100% feature parity with our existing mobile app.


A screen capture of our onboarding flow for web, written in AngularDart — featuring wonky icon fonts.

A screen capture of our onboarding flow for web, written in AngularDart — featuring wonky icon fonts.

Thanks to our relatively clean architecture, I could reuse most of our existing code from our core package. This meant that most of the work in creating the web app went into building the HTML/CSS-based UI layer in AngularDart.

One of the bigger tasks was to rebuild our UI component library from scratch. This included elements such as buttons, text inputs, cards, shadows and a flexible system for common animations and interactions.

Our tactile component in use with a button with inset shadows and touch feedback.

Our <tactile> component in use with a <button> with inset shadows and touch feedback.

To put it briefly, I spent a lot of time rebuilding what we already had in our mobile app but for the web — two months to be exact.

I also had to cut corners in terms of the more complex animations and overall visual beauty. The deadline was approaching fast, and I already had my hands full trying to vertically center divs in CSS.

You can’t always get what you want…

I wanted to take the full Reflectly experience to the web. Some of our users had been requesting it too.

While AngularDart is an excellent framework, spending two months on rebuilding what we already had seemed kind of a wasted effort. Especially if you’re a team of three engineers, out of which only two do frontend.

Our simplified AngularDart web dashboard.

Our simplified AngularDart web dashboard.

We launch new features, bug fixes and experiment with new designs and features every week. It can be very time consuming to build the UI with Flutter first and then vertically center divs with CSS for the web later.

The reality was that we didn’t have the resources to continue bringing Reflectly for the web at the time. We decided to freeze our web development efforts until our situation improved.

…or can you?

Shortly after releasing our AngularDart web app, the first Flutter Live event was held at the Science Museum in London.

At Flutter Live, the Flutter team announced Hummingbird, which was later rebranded to Flutter for web. A couple of months later, I got an invite to try out the early preview of Flutter for web.

Which one is web? Our onboarding flow, written in Flutter — running on iOS and Chrome.

Which one is web? Our onboarding flow, written in Flutter — running on iOS and Chrome.

Being quite excited, I grabbed four beers from the nearby supermarket and went back to our office. Before finishing the last beer, I had our existing codebase (written in Flutter) running on a new platform — the web.

Being able to ship on Android and iOS with a single codebase was one thing, but being able to run it on the web too is quite something else.

Our complex statistics screen, also written in Flutter, running on as a native Android app and in Chrome.

Our complex statistics screen, also written in Flutter, running on as a native Android app and in Chrome.

(Sidenote: during the EAP, the gzipped Javascript file for our app was just about 190kb. It might get smaller in the future, for example with the upcoming non-nullable types for Dart.)

We also did a quick test on how larger layouts would work with Flutter for web and what it would require to adapt to different screen sizes.

Our statistics page made adaptive, in less than three hours.

Our statistics page made adaptive, in less than three hours.

Since our statistics page is composed by laying out several smaller custom widgets on the screen, we can just arrange them differently for desktop.

From initial design handoff to making our statistics page adaptive, it took only three hours of work. Since Flutter has support for adapting to different screen sizes, it’s easy to create container layouts that work for both mobile and web.

The best kind of code is no code

Our AngularDart web app has almost 6000 lines of code.

That might seem small (and it is) but keep in mind that it’s just 5–10% of the features that we have in our Flutter app. That’s 5659 lines of code that I don’t want to maintain and I would be thrilled to throw away.

Lines of code in our AngularDart web app, counted with cloc.

Lines of code in our AngularDart web app, counted with cloc.

My urge to delete all the web-specific code is not because of AngularDart. I actually quite like it. What I don’t like is doing the same work twice.

With our current setup, we have just about 40% code sharing. The remaining 60%? That’s platform specific code in our Flutter and AngularDart apps.

“I just love maintaining two separate codebases that do the same thing!” — said no one ever.

Less web-specific code means fewer web bugs; no web-specific code means no web bugs. At least in the ideal world.

A couple cases of “we’re not there yet”

There are some minor speed bumps in the road preventing us from taking Reflectly to a web browser near you.

No gRPC-web support for Dart (yet)

We are very happy users of gRPC in our Flutter mobile app — every single API call is made with protobufs and gRPC goodness.

Although not Flutter for web specific, one of the most pressing blockers for us is the lack of gRPC-web support for Dart.

The issue ticket for gRPC-Web support has been open for some time in the gRPC-dart repository.

The issue ticket for gRPC-Web support has been open for some time in the gRPC-dart repository.

Although it’s possible to generate a Dart wrapper over the regular gRPC-web library using the TypeScript definitions, that’s not a road we want to take in the long term.

Lucky enough for us, there’s some good progress on the way.

No plugin support for the web (yet)

We are currently using about ten plugins in our Flutter app, but none of them will run on a web browser just yet. This is because there is no web plugin support in Flutter for web as of today.

Once the support is there, we expect those web implementations to come up for most of the existing plugins. While most of them will be easy to migrate, more complicated ones such as sqflite will take a longer time.

Not locking ourselves down

While we are very excited about the current state of Flutter for web, we don’t want to bet our business in an early technology preview.

Flutter on mobile is very forgiving regarding performance. We’re not yet sure if it will be the same story for Flutter for web. According to the Flutter for web README, “performance work is only just beginning”.

So far, we’ve seen that our statistics page has some perceivable lag on bigger screens when the graph animations are running. Time will tell if there are some low-hanging performance fruits up for grabs.

An overly simplified diagram of our current codesharing setup. If Flutter for web doesn’t work out for us, we can continue our AngularDart codesharing efforts.

An overly simplified diagram of our current codesharing setup. If Flutter for web doesn’t work out for us, we can continue our AngularDart codesharing efforts.

We are also worried about how natural Flutter for web feels on each respective platform. Will it feel too much like Adobe Flash, and if it does, is that a problem for our users?

Thanks to our relatively clean architecture, we are not tied into Flutter for web. If for some reason, we decide that it’s not a good fit for us, we will continue our codesharing efforts with AngularDart and build our web UI separately with the native web stack.

“Await-ing” on a very exciting Future

As a small team of three engineers, we couldn’t be more excited about the future of Flutter.

We jumped on board quite early with our Android and iOS apps — and we couldn’t be happier about it. More importantly, our users are extremely happy too. Our app is constantly getting solid five-star ratings and positive reviews.

Our cute little mascot hanging at the very top of Health & Fitness in the US App Store.

Our cute little mascot hanging at the very top of Health & Fitness in the US App Store.

We’ve also recently been lucky enough to hang out with brands like Calm, Headspace and Fitbit in the top ranking apps of the Health & Fitness category.

Using Flutter for mobile has been the best technical decision we’ve made. We can’t wait to say the same about Flutter for web.

]]>
Iiro Krankka
Splitting widgets to methods is an antipattern2018-12-11T00:00:00+00:002018-12-11T00:00:00+00:00https://iiro.dev/splitting-widgets-to-methods-performance-antipatternIt has been almost 6 months since I wrote an article on cleaning up Flutter UI code.

The article had several tips on how to organize your Flutter UI code for less clutter and more readability. And it’s still a quite popular article. However, there was this one tip there that advocated doing something that turned out to be a not that good thing to do.

To escape the Lisp-y bracket hell, I advocated for splitting long build methods into multiple separate smaller methods. The following code sample is entirely nonsensical, but we’ll pretend that this is a snippet that does something really useful.

So, for example, if we have a widget that looks something like this:

Example of a nested build method
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text('Counter: $_counter'),
        Container(
          child: Column(
            children: [
              Text('Hello'),
              Row(
                children: [
                  Text('there'),
                  Text('world!'),
                ],
              ),
            ],
          ),
        ),
      ],
    );
  }
}

Looking at the code, you might get a sense that the nesting level gets a little crazy.

And they do. It would be awesome to reduce the indentation level a little bit. Since widgets can be a little boilerplatey, the first solution that comes into mind is to split the nesting part into a separate method.

Our first intuition might end us having something that looks like this:

Example of a nested build method - split into a separate method
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

  Widget _buildNonsenseWidget() {
    return Container(
      child: Column(
        children: [
          Text('Hello'),
          Row(
            children: [
              Text('there'),
              Text('world!'),
            ],
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text('Counter: $_counter'),

        // The deeply nesting widget is now refactored into a
        // separate method and we have a cleaner build method. Yay!
        _buildNonsenseWidget(),
      ],
    );
  }
}

Problem solved, right? Time to call it a day and go home!

Well, not quite.

The problem with splitting widgets into methods

At first glance, splitting long build methods into small functions makes perfect sense. And you can certainly see this pattern used in the wild a lot. It’s also in heavy use in our codebase at Reflectly - and believe me, we have a bunch of UI code.

Now, 6 months later after my initial article, I’m here to tell you that you shouldn’t do this. If you picked up this bad habit after reading my article, you might be a little pissed at me right now. And you’re welcome. This is what friends do for each other. I’m very sorry.

The issue with this was first brought to my attention by Wm Leler in the comment section of the aforementioned article.

Wm Leler's comment explaining how splitting widgets to functions is a performance antipattern.

Wm Leler's comment explaining how splitting widgets to functions is a performance antipattern.

For those that don’t already know, Wm is a developer advocate for Flutter.

Some of you that read Wm’s comment will have an a-ha moment right now. However, some of you, including me initially, won’t. And that’s fine - we’ll learn what is going on here.

So what’s the problem, really?

Whenever the value of _counter changes, the framework calls the build method. This triggers our widget to rebuild itself. The problem is that _buildNonsenseWidget() gets called every time the value of _counter changes - which ends up rebuilding the widget tree over and over again.

Rebuilding for nothing

In this case, there’s no reason to rebuild that particular widget tree.

The widget tree returned by _buildNonsenseWidget() is stateless by nature - we only need to build it once. Sadly, because the widget tree is built by the _buildNonsenseWidget() method, the Flutter framework rebuilds it every time when the parent widget rebuilds.

Essentially, we’re wasting precious CPU cycles in rebuilding something that doesn’t need to be rebuilt. This happens because from the framework’s perspective, there’s no difference between a long-ass build method and a build method split into multiple smaller methods. Mind you, this is only a simple example - this has a more significant impact on more complex apps.

Splitting long build methods - revisited

The solution for this one is relatively simple, although it results in a couple of extra lines of code. Instead of splitting build methods into smaller methods, we split them into widgets - StatelessWidgets, that is.

When we refactor the previous example, we’ll end up with this:

Example of a nested build method - split into a widget
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Text('Counter: $_counter'),

        // The deeply nesting widget is now refactored into a
        // stateless const widget. No more needless rebuilding!
        const _NonsenseWidget(),
      ],
    );
  }
}

class _NonsenseWidget extends StatelessWidget {
  const _NonsenseWidget();

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: [
          Text('Hello'),
          Row(
            children: [
              Text('there'),
              Text('world!'),
            ],
          ),
        ],
      ),
    );
  }
}

While it’s a little more code, this is much better.

Now the _NonsenseWidget is built only once and all of the unnecessary rebuilds are gone. The parent widget can rebuild itself multiple times, but the _NonsenseWidget doesn’t care - it’s built once and once only.

(This is only one part of the story and applies to const StatelessWidgets - see this answer for what is the difference between functions and classes to create widgets? - a StackOverflow answer by Remi Rousselet.)

Splitting widgets into smaller widgets - more complex examples

You might be thinking that the above was a really simple example and it doesn’t represent the complexity of a real app.

And you’d be right. I recently updated the open source inKino app to follow the advice on this article. For example, I think this is a good sample of splitting widgets into smaller StatelessWidgets in a bigger app.

Conclusion

Instead of splitting you build methods into multiple smaller methods, split them into StatelessWidgets. This way, you won’t be rebuilding your static widget trees multiple times for nothing but wasted CPU cycles. When it comes to optimizing performance of Flutter apps, this is probably one of the lowest hanging fruits.

If you really prefer building your widget trees with methods, you might want to take a look at a package called functional_widget by Remi Rousselet. It alleviates the problems that come with building widget trees with methods by using code generation.

]]>
Iiro Krankka
Why does using Image.network crash widget tests?2018-09-16T00:00:00+00:002018-09-16T00:00:00+00:00https://iiro.dev/image-network-widget-testsRunning widget tests headlessly without any simulator or emulator is pretty dope. It’s quite mindblowing to run some automated taps, swipes, and flings and not need to stare that Waiting for emulator to start... message for minutes.

But then, with the tears of pure joy falling down your cheeks, you try to run a widget test that pumps an Image.network widget. And suddenly those tears are not tears of joy anymore.

A really dumbed-down version that illustrates the problem might look like this:

test/my_widget_test.dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('my image test', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Image.network('https://example.com/image.png'),
      ),
    );

    // Crash!
  });
}

If you try to run the above test, it will crash. Any url you provide, no matter if it’s fake or real, will cause the widget to load the image url and fail instantly.

You’ll be greeted with a long error message that looks like this.

══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════
The following _Exception was thrown resolving an image codec:
Exception: HTTP request failed, statusCode: 400, https://example.com/image.png
...
When the exception was thrown, this was the stack:
#0      NetworkImage._loadAsync (package:flutter/src/painting/image_provider.dart:490:7)
<asynchronous suspension>
#1      NetworkImage.load (package:flutter/src/painting/image_provider.dart:469:14)
#2      ImageProvider.resolve.<anonymous closure>.<anonymous closure> (package:flutter/src/painting/image_provider.dart:266:86)
...
Test failed. See exception logs above.

This might seem weird and annoying at first, but it turns out that this is a quite sane default behavior to have.

Why does this happen?

By default, all HTTP requests in widget tests will always return empty responses, with an HTTP status 400 - Bad Request. In other words - every HTTP request will raise an exception and fail your widget tests. And this is perfectly good default behavior in tests. But why?

Making real HTTP requests in tests is problematic. For one, it makes running tests slower. It also makes tests unpredictable and flaky. They could fail due to poor connectivity issues or server not responding, and those are something out of our control. We don’t want flaky tests depending on outside conditions.

It would be quite nice if there was some way to intercept those requests that the Image.network widget makes in widget tests. And there is.

The solution

The solution is to replace the default HTTP client with one that always responds with a transparent image and an HTTP status of 200 - OK. There’s a sample on how to do this in the Flutter repo and in the widget tests of the flutter_markdown library, but it might get a little boring to copy and paste this thing around.

That’s why I created a little library that wraps this into a nice little package. There are only a few steps to start using it.

Step 1: include it in your dev_dependencies block:

pubspec.yaml
dev_dependencies:
  image_test_utils: ^1.0.0

Step 2: wrap your widget test with a provideMockedNetworkImages method:

test/my_widget_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:image_test_utils/image_test_utils.dart';

void main() {
  testWidgets('my image test', (WidgetTester tester) async {
    provideMockedNetworkImages(() async {
      // Now we can pump NetworkImages without crashing our tests. Yay!
      await tester.pumpWidget(
        MaterialApp(
          home: Image.network('https://example.com/image.png'),
        ),
      );

      // No crashes.
    });
  });
}

(For a more comprehensive sample, see this widget test in the inKino app.)

Step 3: there’s no step three! Just see your widget tests with Image.network widgets pass with flying colors.

Behind the scenes, wrapping your code with provideMockedNetworkImages creates a new Zone, in which the default HTTP client has been replaced with a mocked one. The mocked client always responds with a transparent image and a 200 - OK status. And your Image.network widget tests are now happier than ever.

And yes - provideMockedNetworkImages will override all of your HTTP GET requests. But you generally don’t want to do any API communication in your tests anyway. And luckily anything other than Image.network is quite easy to mock out.

]]>
Iiro Krankka