Slide 1

Slide 1 text

Flutter in Practice Some tips and tricks for writing Flutter apps, from Start to Finish Ana Betts (@anaisbetts)

Slide 2

Slide 2 text

I'm Ana, I'm a software developer, I worked for GitHub on GitHub Desktop, then at Slack on the Slack Desktop app, and now I'm working with Flutter A lot of what I've always worked on in my career has been about cross-platform applications, first with Xamarin and C#, then with Electron, and now with Flutter. hi!

Slide 3

Slide 3 text

Today, I thought I'd talk about some practical advice that I learned, having written a full production application in Flutter. Since Flutter is super new, a lot of talks around it are about Getting Started; this talk is about various things I've learned while putting together a full, for Reals application, end to end Let's talk about fl utter!

Slide 4

Slide 4 text

So, in April, I had surgery to talk more like this, and less like this. As part of that, for three weeks, I wasn't allowed to speak at all. ^ Of course, my solution is to write an app, because when you've got a hammer, everything around you looks like a thumb.

Slide 5

Slide 5 text

There are lots and lots of apps that do text-to-speech, but none of them are usable. They're tech demos, or have thoughtless design. ^ Sirene was designed to be able to keep up in conversation Sirene: A text-to-speech app for day-to- day use

Slide 6

Slide 6 text

Sirene is a cross-platform Flutter app that uses Cloud Firestore + Auth, MLKit, Sentry for error reporting, and Firebase Analytics. Wanted to build a Real App Phrases are stored in The Cloud

Slide 7

Slide 7 text

The canned phrase list is sorted by usage and whether it's a reply ^ The opening list is generated from statistical mining of Friends ^ We do some cool stuff, like use MLKit to figure out what language you wrote text in. Here's why:

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Up front, I will say that this is not a Beacon of Software Design anaisbetts/sirene on GitHub

Slide 11

Slide 11 text

I see a lot of people asking this question, because online, there are a lot of answers How do I choose what Design Pattern to use?

Slide 12

Slide 12 text

MobX?

Slide 13

Slide 13 text

Provider???

Slide 14

Slide 14 text

What even is BLOC? BLOC?

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

This is what really excites me about Flutter - it so fundamentally changes things, that all of the patterns we're used to need to be rethought from First Principles You can Test widgets! Cheaply! As someone who loves thinking about The Zen of Designing Applications, this is Great. What's right for you, as always, depends on your team! Nobody knows! That's why Flutter is Cool.

Slide 17

Slide 17 text

When we design things a certain way, our goal is that we pay something up- front, in order to get a benefit ^ Structure for Structure's Sake isn't usually a benefit! ^ "What are we making easier by making this other thing harder / more work" What makes a Good Design Pattern?

Slide 18

Slide 18 text

I know it's hard to believe, but Code is a way for humans to describe things to each other ^ You know that a pattern is Good when your new hire reads it and understands the intent Design Patterns should Make Code Easier to Understand

Slide 19

Slide 19 text

"Push complexity down" ^ Make it so most people write simple code, and only a few people write complicated code Design Patterns should Make Most Code Faster to Write

Slide 20

Slide 20 text

A design pattern that makes stuff you do all the time filled with boilerplate and ceremony, is Not Good ^ We want Common Stuff to be Easy Design Patterns should Make Common Tasks Easier

Slide 21

Slide 21 text

I don't know about you but I never agree with Computer, but even if you do... "Implicit is better than Explicit, if you and computer agree" - Yukihiro Matsumoto

Slide 22

Slide 22 text

The bigger your team gets, the more that convention and Being Clever will slow you down ^ and the more that explicitness and straightforward code will benefit you "Implicit is better than Explicit, if you, your entire team, and computer, agree"

Slide 23

Slide 23 text

Patterns like, "Just decorate this class to make it a Module" etc etc make it really difficult ^ to understand how the pieces fit together Putting similar code together is Good

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Simple Design Patters: GetIt

Slide 26

Slide 26 text

enum ApplicationMode { debug, production, test } class App extends State { static GetIt locator; App() { locator = App.setupRegistration(GetIt()); } static GetIt setupRegistration(GetIt locator) { // ... } }

Slide 27

Slide 27 text

static GetIt setupRegistration(GetIt locator) { final isTestMode = Platform.resolvedExecutable.contains('_tester'); var isDebugMode = false; // NB: Assert statements are stripped from release mode. Clever! assert(isDebugMode = true); final appMode = isTestMode ? ApplicationMode.test : isDebugMode ? ApplicationMode.debug : ApplicationMode.production; locator ..registerSingleton(appMode) ..registerSingleton(setupRoutes(Router())) ..registerSingleton(FirebaseStorageManager()); // ...

Slide 28

Slide 28 text

if (appMode == ApplicationMode.production) { locator ..registerSingleton(FirebaseAnalytics()) ..registerSingleton(SentryClient(dsn: 'https://the/url')) ..registerSingleton(ProductionLogWriter()); } else { locator ..registerSingleton(DebugFirebaseAnalytics()) ..registerSingleton(DebugLogWriter()); }

Slide 29

Slide 29 text

Widget build(BuildContext context, {Router router, RouteObserver routeObserver}) { // Optional parameter for testing, default for production! routeObserver = routeObserver ?? App.locator(); router = router ?? App.locator(); return MaterialApp( title: 'Sirene', theme: ThemeMetrics.fullTheme(), initialRoute: '/', onGenerateRoute: router.generator, navigatorObservers: routeObserver != null ? [routeObserver] : [], ); }

Slide 30

Slide 30 text

test('Make sure that setting up registrations ends up in test mode', () { var fixture = GetIt()..allowReassignment = true; App.setupRegistration(fixture); expect(fixture(), ApplicationMode.test); });

Slide 31

Slide 31 text

void withLocator(GetIt locator, VoidCallback block) { final oldLocator = App.locator; try { App.locator = locator; block(); } finally { App.locator = oldLocator; } } testWidget('Inject a dependency', (WidgetTester tester) { final locator = App.setupRegistration(GetIt()) ..allowReassignment = true ..registerSingleton(FakeLoginProvider()); withLocator(locator, () { var fixture = LoginWidget(); // Write your test! }); });

Slide 32

Slide 32 text

Because GetIt at the end of the day is just a Map, the startup time is really fast GetIt is Super Fast™

Slide 33

Slide 33 text

GetIt is a "small lego piece" - it's easy to build more complicated things GetIt is Super Dumb™

Slide 34

Slide 34 text

GetIt makes it really easy to replace things in tests. That's the whole point! GetIt is Easy to Test

Slide 35

Slide 35 text

In Dart, every class is an interface, whose definition is the public members of the class ^ This means, you don't have to wrap 3rd party classes as often Implicit Interfaces are Cool

Slide 36

Slide 36 text

abstract class LoginManager { // This is a class from Firebase, but because // All Classes Are Interfaces, I can often just // Use It instead of making a silly wrapper. UserInfo get currentUser; Observable getAuthState(); Future login(); Future logout(); Future ensureNamedUser(); }

Slide 37

Slide 37 text

class DummyTestUser implements UserInfo { // ... }

Slide 38

Slide 38 text

class DebugFirebaseAnalytics implements FirebaseAnalytics { @override FirebaseAnalyticsAndroid get android => null; @override Future logEvent({String name, Map parameters}) async { debugPrint("Analytics: $name, $parameters"); } // ... }

Slide 39

Slide 39 text

When in Doubt, ask your React Friends

Slide 40

Slide 40 text

flutter_hooks class Example extends StatefulWidget { final Duration duration; const Example({Key key, @required this.duration}) : assert(duration != null), super(key: key); @override _ExampleState createState() => _ExampleState(); } class _ExampleState extends State with SingleTickerProviderStateMixin { AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: widget.duration); } @override void didUpdateWidget(Example oldWidget) { super.didUpdateWidget(oldWidget); if (widget.duration != oldWidget.duration) { _controller.duration = widget.duration; } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } }

Slide 41

Slide 41 text

flutter_hooks class Example extends HookWidget { final Duration duration; const Example({Key key, @required this.duration}) : assert(duration != null), super(key: key); @override Widget build(BuildContext context) { final controller = useAnimationController(duration: duration); return Container(); } }

Slide 42

Slide 42 text

Dart Linter: It's Good, Folks

Slide 43

Slide 43 text

unawaited() BAD: // Returns Future, but nobody can tell sharedPrefs.setBool(INTRO_SEEN, true) GOOD: // We're explicitly saying, "I don't care about the result" unawaited(sharedPrefs.setBool(INTRO_SEEN, true));

Slide 44

Slide 44 text

prefer_const_constructors actually makes your app faster

Slide 45

Slide 45 text

Since Widgets are immutable objects, we can reuse stuff return Container( color: Theme.of(context).backgroundColor, child: Padding( padding: EdgeInsets.all(8),

Slide 46

Slide 46 text

return Container( color: Theme.of(context).backgroundColor, child: Padding( padding: const EdgeInsets.all(8),

Slide 47

Slide 47 text

Flexbox is Amazing!

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

return Flex( direction: Axis.vertical, children: [ UsernameCard(), Expanded( child: InstagramImage() ), Flex( direction: Axis.horizontal, children: [LikeButton(), CommentButton(), ShareButton()], ), LikesText(), UsernameAndCaption(), Comments(collapsed: true), ], );

Slide 51

Slide 51 text

Check Flutter into your app as a submodule

Slide 52

Slide 52 text

y tho?

Slide 53

Slide 53 text

Setting it up git submodule add https://github.com/flutter/flutter cd ./vendor/flutter git checkout v1.7.0 cd ../../ git add .gitmodules ./vendor git commit -m "Set Flutter to v1.7.0"

Slide 54

Slide 54 text

Introducing flutterw macOS / Linux: #!/bin/bash set -euo pipefail DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" export PATH="$DIR/vendor/flutter/bin:$PATH" exec flutter $@

Slide 55

Slide 55 text

Introducing flutterw Windows: "%~dp0vendor\\flutter\\bin\\flutter.bat" %*

Slide 56

Slide 56 text

{ "dart.flutterSdkPath": "vendor/flutter", "dart.analysisExcludedFolders": [ "vendor" ], "dart.doNotFormat": [ "vendor/**/*.dart" ], }

Slide 57

Slide 57 text

Demo!

Slide 58

Slide 58 text

Thanks for your time! @anaisbetts on GitHub / Twitter