iRyanBell

Learning How To Flutter - Part 3

May 17, 2019

Learning How To Flutter - Part 3

We continue our mobile app development journey with a closer look at object-oriented practices vs. a functional design pattern.

Initially, I created an AppBar widget by adding it directly into the Flutter project’s main.dart file:

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

This worked well, but I wanted to create a more compartmentalized structure. So, it became:

final AppBar myAppBar = AppBar(
    ...
)

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

Again, this worked well until I needed to interact with the outer context for navigating to the dialog overlay:

Each widget has its own BuildContext, which becomes the parent of the widget returned by the StatelessWidget.build or State.build function. (And similarly, the parent of any children for RenderObjectWidgets.)

In particular, this means that within a build method, the build context of the widget of the build method is not the same as the build context of the widgets returned by that build method. This can lead to some tricky cases. For example, Theme.of(context) looks for the nearest enclosing Theme of the given build context.

If a build method for a widget Q includes a Theme within its returned widget tree, and attempts to use Theme.of passing its own context, the build method for Q will not find that Theme object. It will instead find whatever Theme was an ancestor to the widget Q.

If the build context for a subpart of the returned tree is needed, a Builder widget can be used: the build context passed to the Builder.builder callback will be that of the Builder itself.

Coming from a functional background, my initial thought process was to wrap the component in a createAppBar utility, which would take a context argument and return a new component with the attached context:

AppBar createAppBar (BuildContext ctx) {
  return AppBar(
    ...
  );
}

So at this point, my app_bar.dart began to feel more like an app_bar_util.dart. To better convey this pattern, I restructured my project as follows:

Flutter Project: App Bar Organization

When in flutter

Going back over the logic, I read about subclass extensions in Dart:

https://dart.dev/guides/language/language-tour#extending-a-class

If I chose to follow this design pattern, it would allow me to inherit context into my AppBar without having to explicitly perform this operation — I could reason about my file as an AppBar widget, rather than think about it in terms of an AppBar widget maker. Conceptually, this might make more sense when just a single bar is used across all views.

Since my GSDistAppBar is just a plain AppBar with some default attributes, I went with the following structure:

class GSDistAppBar extends AppBar {
	GSDistAppBar() : super(
		...
	);
}

Now the focus turned to my IconButton generator function: createIconButton, a relic from my previous design pattern. I questioned whether or not I would still want to conceptualize this as a process for creating buttons with two arguments flowing into the function — details and context, and one IconButton flowing out. Or, if this was again like the AppBar example, where a class was more idiomatic than a function. As this language does seem to encourage the former pattern, I rewrote the code block in an object-oriented style.

My first naive attempt was based on previous AppBar class extension’s super keyword:

class GSDistIconButton extends IconButton {
    GSDistIconButton({
        IconData icon,
        String label,
        Function action,
        Key key,
    }) : super(
        iconSize: 24.0,
        icon: Icon(icon),
        tooltip: label,
        onPressed: action,
    );
}

You can see in this example however, there’s no BuildContext attached to the anonymous onPressed action lambda. To achieve this functionality would require going full-OOP mode and overriding a StatelessWidget instead of an IconButton directly, then providing a new build definition to return the desired component.

import 'package:flutter/material.dart';

class GSDistIconButton extends StatelessWidget {
	final IconData icon;
	final String label;
	final Function action;

	GSDistIconButton({
		this.icon,
		this.label,
		this.action,
		Key key,
	});

	
	Widget build(BuildContext ctx) {
		return IconButton(
			key: key,
			iconSize: 24.0,
			icon: Icon(icon),
			tooltip: label,
			onPressed: () => action(ctx),
		);
	}
}

Compare that to the previous function, and you can see why I generally prefer to write in a functional style:

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

The second format feels like it has a higher signal-to-noise ratio.

Continuing on

This app feels like it could benefit from a sidebar / drawer. In the Flutter Cookbook, there’s a great example:

https://flutter.dev/docs/cookbook/design/drawer

class GSDistDrawer extends StatelessWidget {
	
	Widget build(BuildContext ctx) {
		return Drawer(
			child: Container(
				color: Colors.red,
			),
		);
	}
}

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

Flutter Project: Drawer

Now, we have a place for managing our lists list.

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

In the next chapter, we’ll edit the pubspec.yaml spec file to import in shared preferences, speech recognition, and share functionality. Then build a custom DismissibleTodoItem component. Stay tuned!