Learning How To Flutter - Part 2 | iRyanBell.com
iRyanBell

Learning How To Flutter - Part 2

May 16, 2019

Learning How To Flutter - Part 2

We begin our mobile development journey at the base of the Flutter Starter boilerplate project.

From the VS Code Command Palette, the command Flutter: New Project provided by the Flutter VS Code Extension will initialize a new project folder with a starter template (https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter).

After calling up an iOS Simulator and patching a running instance through with flutter run -d <ID>, we’re looking at this beauty:

Flutter Starter

This application is largely described by a single main.dart file inside the project’s root lib directory:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
	
	Widget build(BuildContext context) {
		return MaterialApp(
			title: 'Flutter Demo',
			theme: ThemeData(
				primarySwatch: Colors.blue,
			),
			home: MyHomePage(title: 'Flutter Demo Home Page'),
		);
	}
}

class MyHomePage extends StatefulWidget {
	MyHomePage({Key key, this.title}) : super(key: key);
	final String title;

	
	_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
	int _counter = 0;

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

	
	Widget build(BuildContext context) {
		return Scaffold(
			appBar: AppBar(
				title: Text(widget.title),
			),
			body: Center(
				child: Column(
					mainAxisAlignment: MainAxisAlignment.center,
					children: <Widget>[
						Text(
							'You have pushed the button this many times:',
						),
						Text(
							'$_counter',
							style: Theme.of(context).textTheme.display1,
						),
					],
				),
			),
			floatingActionButton: FloatingActionButton(
				onPressed: _incrementCounter,
				tooltip: 'Increment',
				child: Icon(Icons.add),
			),
		);
	}
}

To switch in a dark theme, it’s just a matter of tacking on a brightness: Brightness.dark setting to the MaterialApp theme setting.

Flutter Starter: Dark

void main() => runApp(
	MaterialApp(
		home: GSDist(title: 'GSDist'),
		theme: ThemeData(
			brightness: Brightness.dark,
			primarySwatch: Colors.blue,
		),
	),
);

Material’s color selection recommendations assist developers to improve branding and accessibility with luminance-based contrast ratio guidelines.

https://material.io/design/usability/accessibility.html#color-contrast

Color and contrast
Contrast ratios represent how different a color is from another color, commonly written as 1:1 or 21:1. The higher the difference between the two numbers in the ratio, the greater the difference in relative luminance between the colors. The contrast ratio between a color and its background ranges from 1-21 based on its luminance (the intensity of light emitted) according to the World Wide Web Consortium (W3C).

The W3C recommends the following contrast ratios for body text and image text: Small text should have a contrast ratio of at least 4.5:1 against its background. Large text (at 14 pt bold/18 pt regular and up) should have a contrast ratio of at least 3:1 against its background.

The color system
Your app’s primary and secondary colors, and their variants, help create a color theme that is harmonious, ensures accessible text, and distinguishes UI elements and surfaces from one another.

Color indicates which elements are interactive, how they relate to other elements, and their level of prominence. Important elements should stand out the most.

Text and important elements, like icons, should meet legibility standards when appearing on colored backgrounds, across all screen and device types.

Reinforce your brand by showing brand colors at memorable moments that reinforce your brand’s style.

https://material.io/design/color/

🚀 Onward!

To tighten in on the logic, I’m going to split this project into smaller .dart files. Personally, I tend to prefer short, specific files with a wide line column ruler (120 characters).

Flutter Project Organization

Main:

import 'package:flutter/material.dart';
import 'app.dart';

void main() => runApp(gsdistApp);

Theme:

import 'package:flutter/material.dart';

final ThemeData gsdistTheme = ThemeData(
	brightness: Brightness.dark,
	primarySwatch: Colors.blue,
);

Resources:

final Map gsdistResources = {
	'English': {
        'appBar': {
		    'title': 'GSDist',
        },
	},
}; 

App:

import 'package:flutter/material.dart';
import 'gsdist.dart';
import 'theme.dart';

final MaterialApp gsdistApp = MaterialApp(
	home: GSDist(),
	theme: gsdistTheme,
);

Gsdist:

import 'package:flutter/material.dart';

class GSDist extends StatefulWidget {
	
	GSDistState createState() => GSDistState();
}

class GSDistState extends State<GSDist> {
	int _counter = 0;

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

	
	Widget build(BuildContext context) {
		return Scaffold(
			appBar: AppBar(
				title: Text(widget.title),
			),
			body: Center(
				child: Column(
					mainAxisAlignment: MainAxisAlignment.center,
					children: <Widget>[
						Text(
							'You have pushed the button this many times:',
						),
						Text(
							'$_counter',
							style: Theme.of(context).textTheme.display1,
						),
					],
				),
			),
			floatingActionButton: FloatingActionButton(
				onPressed: _incrementCounter,
				tooltip: 'Increment',
				child: Icon(Icons.add),
			),
		);
	}
}

The AppBar component from Material is documented at: https://docs.flutter.io/flutter/material/AppBar-class.html

To follow the design from my prototype, I’d like to put a Delete icon in the Leading slot, then Complete, Share, Dictate, and Create icons in the Actions slot.

To accomplish this, I’ll create a new .dart file in ./lib named app_bar.dart, then import it into the appropriate spot in my main Scaffold.

Getting to work

The map function is defined as:

Iterable<T> map<T>(T f(E e)) => MappedIterable<E, T>(this, f);

https://api.dartlang.org/stable/2.3.0/dart-core/Iterable/map.html

In other words, it takes a list, runs each item through a lambda, and returns a transformed list.

With a .map() function, I’ll create a utility that will allow us to abstract an icon’s name/action into List<Map> data structure so that we can tuck those away into something like app_bar_items.dart.

So, we get something like this:

Utils

import 'package:flutter/material.dart';

IconButton createIconButton(Map iconDetails, BuildContext ctx) {
	return IconButton(
		iconSize: 24.0,
		icon: Icon(iconDetails['icon']),
		tooltip: iconDetails['label'],
		onPressed: () => iconDetails['action'](ctx),
	);
}

App Bar Items

import 'package:flutter/material.dart';
import 'resources.dart';

final List<Map> gsdistAppBarLeading = [
	{
		'label': gsdistResources['English']['appBar']['delete'],
		'icon': Icons.delete,
		'action': (ctx) {},
	}
];

final List<Map> gsdistAppBarActions = [
	{
		'label': gsdistResources['English']['appBar']['complete'],
		'icon': Icons.check,
		'action': (ctx) {},
	},
	{
		'label': gsdistResources['English']['appBar']['share'],
		'icon': Icons.share,
		'action': (ctx) {},
	},
	{
		'label': gsdistResources['English']['appBar']['dictate'],
		'icon': Icons.mic,
		'action': (ctx) {},
	},
	{
		'label': gsdistResources['English']['appBar']['create'],
		'icon': Icons.add,
		'action': (ctx) {},
	},
];

App Bar

import 'package:flutter/material.dart';
import 'app_bar_items.dart';
import 'resources.dart';
import 'utils.dart';

AppBar createAppBar (BuildContext ctx) {
  return AppBar(
    leading: gsdistAppBarLeading.map(
      (item) => createIconButton(item, ctx)
    ).first,
    title: Text(gsdistResources['English']['appBar']['title']),
    actions: gsdistAppBarActions.map(
      (item) => createIconButton(item, ctx)
    ).toList(),
  );
}

Gsdist

import 'package:flutter/material.dart';
import 'app_bar.dart';

class GSDist extends StatefulWidget {
	
	GSDistState createState() => GSDistState();
}

class GSDistState extends State<GSDist> {
	int _counter = 0;

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

	
	Widget build(BuildContext context) {
		return Scaffold(
			appBar: gsdistAppBar,
			body: Center(
				child: Column(
					mainAxisAlignment: MainAxisAlignment.center,
					children: <Widget>[
						Text(
							'You have pushed the button this many times:',
						),
						Text(
							'$_counter',
							style: Theme.of(context).textTheme.display1,
						),
					],
				),
			),
			floatingActionButton: FloatingActionButton(
				onPressed: _incrementCounter,
				tooltip: 'Increment',
				child: Icon(Icons.add),
			),
		);
	}
}

Flutter App Bar

Now, it’s time to clean out the unused components from the starter.

Gsdist

import 'package:flutter/material.dart';
import 'app_bar.dart';

class GSDist extends StatefulWidget {
	
	GSDistState createState() => GSDistState();
}

class GSDistState extends State<GSDist> {
	
	Widget build(BuildContext ctx) {
		return Scaffold(
			appBar: createAppBar(ctx),
			body: Container(),
		);
	}
}

This application will make use of a floating dialog box for confirming actions and adding data, so let’s set up a way to trigger that action from the AppBar and interact with the main context.

{
	'label': gsdistResources['English']['appBar']['create'],
	'icon': Icons.add,
	'action': (ctx) {
		showDialog(
			context: ctx,
			barrierDismissible: false,
			builder: (ctx) {
				return AlertDialog(
					title: Text('Hello World!'),
					actions: [
						FlatButton(
							child: Text('Close'),
							onPressed: () {
								Navigator.of(ctx).pop();
							},
						),
					],
				);
			},
		);
	},
},

Flutter Dialog

This project is available on GitHub at:
https://github.com/iRyanBell/learning-how-to-flutter_gsdist-part-2

In the next chapter, we’ll look at the process for creating a custom task-item swipe component by extended a Dismissible Material component class. Out of the box, a Dismissible component gives us the ability to swipe an item to remove it from the screen. But, what we need is something that allows us to swipe an item to a different state (eg. “Complete.“) After that, it’s onto audio transcriptions and data storage.

https://docs.flutter.io/flutter/widgets/Dismissible-class.html

Dismissible https://www.youtube.com/watch?v=iEMgjrfuc58

An interesting problem to think about is how we might approach transforming a stream of audio into a transcribed, itemized list of items.

Through the microphone input, we receive data each time the entire buffer updates. For example, if the microphone picks up “four…” you will receive a string with the message “four,” however, once the microphone hears “-teen”, the update function is called again with “14.” Suppose we continue with “apples and 15 bananas” — would it make sense to split on the utterance of the word “and”? Probably not, in the case of a single item like “a peanut butter and jelly sandwich” — it’s not “a peanut butter” and “jelly sandwich.”

The trick will be to use a timer between phrases to detect natural splice points to apply to the buffer.

Junior developer disclaimer 🤓

It’s important to stress at this point that I’m very new to the Dart language and Flutter framework. I’ve essentially just run the Installer and cracked the documentation open myself to the first Getting Started section.

You’re witnessing this project unfold as I experiment with the tooling and find my way through the maze. This is less of a lesson on best practices, and more a story of a practical approach to exploration and the adventure of seeing where things lead from humble beginnings.

As developers, where several of these new frameworks/languages seem to spring into existence each year, we have to make the best of our “junior developer for life” reality.

Writing an application in a brand new programming language or framework is not unlike an English speaker wanting to both learn Japanese and publish an article in Japanese at the same time. In such a situation, one often knows what they want to say but not how to say it. It’s this process of learning how to express similar ideas in different ways that can improve our conceptual flexibility. There are few things in life can change the way we think — learning a new programming language is one of those things.

This transitional skill is what programming ultimately teaches the coder. We should strive to hone the skill of learning how to write by learning how to learn how to write.