Building a Flutter app, part 4: refactors, improvements to the mock data, components

Welcome to part 4 of my Flutter App development journal, where I document my process of building a “grocery list” app from scratch.

In this post: Advanced interactions, improvements to the mock data, refactoring repeated code into components.

In the previous post, I built and linked up many of the app’s major routes, but there isn’t much to do on them yet. Changes made on one page aren’t really reflected on another, and a lot of code is repeated.

Now that most pages are represented, I wanted to refine the mock data, improve the way it gets passed around, refactor repeated code into components, and add more interactions such as moving items between lists.

This will be a short post, but this work should be done before moving onto the next step, which is hooking the app up to Firebase.

Refactoring the mock data

The mock data was useful for filling in the UI, but now I’d like to refine it.

  • Items need to exist independent of any particular list, so that they may appear on multiple lists and be moved between lists with ease
  • Lists should be more self-contained so that their data isn’t passed as multiple params; this object should contain the lists’s ID, name, and an array of item IDs
  • Default to local storage and pass data around, syncing with server whenever possible, but don’t prevent usage of the app in “offline” mode

The first thing I did was refactor it so the onTap functions all use a callback method passed in from main.dart or existing_shopping_list.dart. This meant I could finally remove all the “if … else” logic that checked what type of list the item belonged to from list_item.dart. (Sorry you had to see that, that was not my finest code, lol)

Here’s kind of a snippet of how that looks, from main.dart:

...

_editList(ShoppingList list) {
    Navigator.pushNamed(context, ExistingList.routeName, arguments: ExistingListArguments(list));
  }

@override
  Widget build(BuildContext context) {

    const headerShoppingLists = "Shopping Lists";
    const headerStores = "Stores";

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: LayoutBuilder(
        builder: (BuildContext context, BoxConstraints viewportConstraints) {
          return SingleChildScrollView(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.start,
              children: <Widget>[
                ItemListHeader(text: headerShoppingLists),
                ItemList(list: shoppingLists, listType: 'shopping list', onItemTap: _goToList, onInfoTap: _editList),

And then, over here in item_list.dart, we continue passing the onItemTap and onInfoTap method handles down:

import 'package:flutter/material.dart';

import 'add_new.dart';
import 'delete_all.dart';
import 'list_item.dart';

class ItemList extends StatelessWidget {

  final list;
  final listType;
  final onItemTap;
  final onInfoTap;

  ItemList({Key key, @required this.list, @required this.listType, @required this.onItemTap, @required this.onInfoTap});

  @override
  Widget build(BuildContext context) {

    int getCount(item) {
      if (listType == 'shopping list') {
        return item.itemIDs.length;
      } else {
        return list.length;
      }
    }

    return ListView.builder(
        shrinkWrap: true, // gives it a size
        primary: false, // so the shopping and store lists don't scroll independently
        itemCount: list.length + 1,
        itemBuilder: (BuildContext context, int index) {
          if (index == list.length) {
            if (listType == 'crossedOff') {
              return DeleteAll();
            } else { // store, shopping list
              return AddNew(listType: listType);
            }
          } else {
            return ListItem(item: list[index], listType: listType, count: getCount(list[index]), onTap: onItemTap, onInfoTap: onInfoTap);
          }
        }
    );
  }
}

And then in list_item.dart, the passed-in methods are invoked on onPressed or on onTap, and this is where I pass back the entire item (we’ll see if I regret that decision later, for now passing the whole item makes it easy to pick it apart on the next page for its ID, name, and contents).

@override
Widget build(BuildContext context) {
return ListTile(
title: Text(buildTitleString(), style: (listType == 'crossedOff' ? TextStyle(decoration: TextDecoration.lineThrough) : TextStyle(decoration: TextDecoration.none))),
subtitle: Text(buildSubtitleString()),
leading: FlutterLogo(),
trailing: IconButton(
icon: Icon(Icons.info),
onPressed: () => onInfoTap(item),
),
onTap: () => onTap(item), //handleTap(context),
);
}

There are some other refactors in this pull request, too:

  • I added IDs to the mock data sets and restructured some of them
  • I renamed ome of the default Flutter boilerplate (“MyApp” is now “GroceryGoApp”, etc.)
  • I moved some existing code into new components, such as delete_all.dart and add_new.dart.

You can view all of these changes here.

On to database stuff

At this point, I can either continue building with mock data, passing it around and modifying it locally but really, it’s time to start putting it into/taking it out of a database. On to part 5!

OSU eCampus CS467 Capstone review and recap

This post is part of an ongoing series recapping my experience in Oregon State University’s eCampus (online) post-baccalaureate Computer Science degree program. You can learn more about the program here.

Six-word summary: One big project, make it count!

You group up with two other students and spend the entire quarter making a project together.

CS467 Review

CS476 is one quarter-long project with a 3-person team of your choosing. If you can’t find groupmates, you can volunteer to be assigned a team. I looked for my groupmates a quarter ahead of time and I think it’s how I ended up with such a high performing group. My team was great.

You can pick one of the instructor-provided projects (which is what my team did) or you can come up with something of your own design, but you’ll have to “sell it” to the instructor or a TA to get approval to make it.

There were a good 20 or so instructor-provided projects to pick from and about half of them seemed to involve developing some kind of mobile app, so if you aren’t keen on developing an app (which some people in the class weren’t) this might rub you the wrong way. I wanted to build a React website and, luckily, there were a couple suitable prompts.

There is very little (if any) instructor/TA feedback on what you make. You won’t get any feedback on your code or what you make, or at least no one on my team did.

Every week you record a 3-4 minute video demonstrating the progress you personally made, and at the end of the quarter someone on your team will put together a poster that summarizes the project.

The class doesn’t really “teach” anything – there’s no lectures or worksheets or tests, it’s just there to make sure you make progress each week. We had to make a presentation poster at the end, but I have no idea who actually saw it (our project showcase was held virtually in the midst of COVID-19 and I couldn’t attend due to my kids’ daycare closing).

If I have any complaint it’s that I had to spend $2000 to just… do a project. I can do that by myself for free. (And I have done that: see OSU Course Explorer, my collection of WordPress plugins, Scene Together, Amazin’ Link Checker). But my group was solid and we made something cool, so it was a positive experience overall.

Time commitment

The class’s instructions tell everyone to spend 10 hours a week on it but they also lay out a list of requirements that, in my opinion, could not be achieved if everyone on the team just shut their laptop at the 10-hour mark. I put in around 20-25 hours each weeks.

Tech stack

Since everyone in the team either already worked in web development (or aspired to), choosing React for the project felt relevant and meaningful.

We used:

  • React 16.8
  • TypeScript
  • Google Firebase and Firestore
  • Web speech API and Azure Speech Services
  • Node.js
  • Heroku

My contributions

Just to give you a feel for what an individual might contribute to a quarter-long project, here’s a list of my major contributions:

  • Project setup, structure, naming conventions
  • Early working examples of routes, components, and features to serve as a template/guide for the rest of the project
  • User account creation and management (using Firebase Authentication API)
  • User schema in Firebase to hold additional user account data
  • All of the user-facing forms
  • Account linking system, whereby one user can “invite” another to follow and that other account either accepts or declines the invitation
  • Settings page where account links are viewed, deleted, and account profile details are changed
  • Researching, prototyping, and implementing the “voice to text” feature which required access to the on-device microphone and camera
  • Prototype work for the “photo reply” feature
  • “Quality of life” improvements, such as being able to refresh app and stay on the page you were on, the header collapsing into a drawer for mobile users, form validation, supported browser notification text
  • Responsive app UI works on desktop, iPad, and mobile (in both vertical and horizontal layout)
  • CSS-based solution to create “envelope” graphic that has open/closed states
  • Created art assets and icons for the project
  • App “look and feel”
  • “Sent” animation
  • Heroku pipeline owner
  • Contributed to fixing bugs found by teammates

My teammates had similarly long lists of accomplishments. We arranged the workload such that each of us owned a full “slice” of the app, so they took on the creation, sending, and display of messages between users. Everyone owned the “full stack” of their features, so (hopefully) everyone finished the project feeling like they owned a section of it top to bottom.

What we made

We called our app “Hola, Oma!” and it was based on a prompt provided by the instructor. We built a messaging app wherein the interface is simplified for the “grandparent” user but more complex for the “post creator” user. The user can send text, photos, or videos, and the recipient can respond with text or a selfie. We implemented “voice to text” to make replying with a message simpler for a less tech-savvy user.

Here’s our poster:

Main screen for the “grandparent” user:

User log-in flow and “link up” screen on mobile (for “post creator” type user):

Post-creator type user’s home screen (mobile and desktop):

Post creation (mobile and desktop):

By the end of the quarter the app felt pretty robust! We completed all of the goals we set out to achieve, and even had time for some nice extras. I think it helped a lot that everyone on my team had either an internship or an industry job already, so it was truly a team of professionals.

You can view our project’s GitHub repo here.

In conclusion

Capstone was the final course in my OSU CS studies and it was great to end on a high note. You can read my reviews of other OSU CS courses here.

Building a Flutter app, part 3: navigating between app screens using routes and Navigate.to

Welcome to part 3 of my Flutter App development journal!

In this post: Building more pages for the app, linking them together so the user can “click around” and navigate between the different screens.

To view the project repo as it was during this step, click here.

Making the list items respond to taps

In Part 2 we left off with a main screen that displays some mock data and can be scrolled, but nothing really goes anywhere or does anything yet. Making the list items interactive is the next step. The ListTile class already supports an onTap property, so adding that is easy.

Here’s some basic “wiring” that causes tapping on any of the list items to trigger a function I added called goToStub.

main_screen_list.dart

...
goToStub(destination) {
  print(destination);
}

@override
Widget build(BuildContext context) {
 
...

itemBuilder: (BuildContext context, int index) {
  if (index == list.length) {
    return ListTile(
        title: Text("Add new " +  listType  + "..."),
        onTap: () => goToStub("Going to: Add new " + listType),
    );
  } else {
  ...

Be sure to give the onTap an anonymous function structured like this:

onTap: () => goToStub("string here") 

instead of a function call this:

onTap: goToStub("string here")

If you do the latter, it’ll cause goToStub(...) to get called immediately on page load. This doesn’t “do” anything yet, it’s just a print statement that proves the wiring works. Now that we know it works, we can build a second page and make it so the onTap takes you there.

To see the complete code example for the onTap and stub function wiring, take a look at this commit.

Navigating between different pages in the app

In this step, I’ll make it so tapping a list item opens a different page in the app.

There are at least four different user flow “paths” out of this main page:

  • Tap a shopping list => open that shopping list
  • Tap a store => open that store’s management page
  • Tap “Add a new shopping list…” => take the user to a form where they can add a new shopping list
  • Tap “Add a new store…” => open a form where the user can enter a new store

I’m going use mock data for these UI pages; I think building the UI will make it easier to determine the data structures.

Building the ‘Add new shopping list’ page UI

First, I created a views folder and added a new file within: views/new_shopping_list.dart

import 'package:flutter/material.dart';

class NewShoppingList extends StatefulWidget {

  static const routeName = 'newShoppingList';

  NewShoppingList({Key key});

  @override
  _NewShoppingListState createState() => _NewShoppingListState();
}

class _NewShoppingListState extends State<NewShoppingList> {

  void saveList(BuildContext context) {
    print("Saving new list");
    Navigator.of(context).pop();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Add new shopping list"),
      ),
      body: Center(
        child: Padding(
          padding: EdgeInsets.all(20),
          child: Column(
            children: [
              TextFormField(
                autofocus: true,
                decoration: InputDecoration(
                    labelText: 'Shopping list name',
                    border: OutlineInputBorder()),
              ),
              RaisedButton(
                onPressed: () => saveList(context),
                child: Text('Save'),
              ),
            ],
          ),
        ),
      )
    );
  }
}
  • It is a stateful widget because the user is going to interact with it and it has a very simple “saveList” method that doesn’t do anything except send the user back to the main screen.
  • I defined the name of this route near the top, look for static const routeName = 'newShoppingList';
  • I wrapped everything in a Padding widget because without it, the text field was right up against the screen edges.
  • There’s a lot more to do to make the form actually work, but I just wanted to build the UI and wire it all together before getting into that stuff

Next, I went back to main.dart and above the MaterialApp return added a routes object. This is the same pattern Flutter’s docs teach.

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    var routes = {
      NewShoppingList.routeName: (context) => NewShoppingList(),
    };

    return MaterialApp(
      routes: routes,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Grocery Go!'),
    );
  }
}

Finally, in main_screen_list.dart I changed the onTap to call a new method I added to the file called goToNew.

onTap: () => goToNew(context, listType),
goToNew(context, destination) {
  if (destination == 'shopping list') {
    Navigator.pushNamed(context, 'newShoppingList');
  }
}

I pass it listType, which isn’t ideal because it’s just a string that looks like “shopping list”, but for this first pass implementation I didn’t want to do anything more than just what was needed to illustrate how this works. We will refactor this soon.

You can see all of those changes in this commit.

Here’s what we have now. Note that it doesn’t actually save “New list”, it just sends me back to the main page.

Building the ‘Add new store’ page UI

The ‘add a new store’ page is very similar to the ‘add a new shopping list’ page, so I copied and pasted the contents of new_shopping_list.dart into a newly created file, new_store.dart and built it out from there. I also updated the routes object to contain the new route in main.dart:

var routes = {
NewShoppingList.routeName: (context) => NewShoppingList(),
NewStore.routeName: (context) => NewStore(),
};

You can see all of the code for new_store.dart in this commit.

The result:

We will come back to this page later to hook it up to a mapping API for choosing the store location, but for now this is enough to get the route wired in.

Building the ‘Shopping list’ page

This page is where the user sees what’s on a specific shopping list, adds and removes items to the list, and manages the list itself. Unlike the “create new…” routes I made earlier, this route is going to need some data passed into it when the user navigates to it. The Flutter documentation has a great guide for passing parameters into a route.

My new route takes a listID and a listName. (I may refactor this to be a single list object later on that contains these things as properties but for now I’ll just go with passing in these two things separately.)

views/existing_shopping_list.dart

class ExistingShoppingListArguments {
  final String listID;
  final String listName;
  ExistingShoppingListArguments(this.listID, this.listName);
}

class ExistingShoppingList extends StatelessWidget {

  static const routeName = '/existingShoppingList';

  @override
  Widget build(BuildContext context) {

    final ExistingShoppingListArguments args = ModalRoute.of(context).settings.arguments;

    // now I can do stuff with args.listID, args.listName

    return Scaffold(
      ...

The “existing shopping list” page is similar to the main page in that it’ll be two lists in a column together, where the top list is the “active” part of the list, sorted by category and the bottom list is crossed off items.

return Scaffold(
  appBar: AppBar(
    title: Text(args.listName + ", id: " + args.listID),
  ),
  body: LayoutBuilder(
    builder: (BuildContext context, BoxConstraints viewportConstraints) {
    return SingleChildScrollView(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.start,
        children: <Widget>[
          ItemListHeader(text: "Category/Aisle here"),
          ItemList(list: list, listType: 'item'),
          ItemListHeader(text: "Crossed off"),
          ItemList(list: crossedOff, listType: "crossedOff"),
        ],
      ),
    );
  }),
);

Refactor notes: I renamed MainScreenList to ItemList and MainScreenListHeader to ItemListHeader, added an Item model, and expanded the listType handling to vary what is displayed for a single Item based on what kind of item it is.

Here’s where we’re at now, after this code was merged in:

I also added two new libraries to the project for date parsing:

These refactors and additions can be found in this pull request. (I’m switching to pull requests instead of individual commits now that the work for each step is getting more complex.)

Building the ‘Store details’ page UI

This is the page where the user edits an existing store.

I rolled the code for this one into the previous pull request, look for views/existing_store.dart to see how I built this one and how I pass in an existing store for editing.

(Editing a store doesn’t do anything yet, this is just more UI work.)

Now the user can tap around and change what page of the app they are on!

Routes and UI for adding and editing list items

There are just a few major routes left to build:

  • adding a new list item
  • editing an existing list item
  • user account stuff – I’ll save this stuff for later

Adding a new item

Items are individual things (“eggs”, “bread”, etc.) on the shopping lists. To keep the adding of new items quick and easy, the user only needs to enter the item’s name to add it to the list.

For now, the new_item.dart page is simple:

Later this will be expanded to running a real-time search on the user’s existing items so the user can quickly pick an existing item with a similar name instead of entering a brand new item every time they want to add “milk” to the list.

Something to note on this route is that the listID is passed as an argument. This is so the newly created item can be assigned a list when it is created. It uses the same “how to pass an argument to a route” pattern described here in the Flutter docs.

Editing an existing item

When viewing an item in the list, the little “i” icon on the right can be tapped to open up the item’s details.

(For now, tapping the item name itself will cross the item off the list. I don’t know if I like this particular UX – I keep tapping the item name hoping to edit it, only to remember I need to tap the “info” icon to edit it. This is good enough for now, though.)

Tapping the “i” opens up existing_item.dart where the user can modify any of the item’s properties, including:

  • name (such as “eggs” or “cut frozen green beans”).
  • quantity, which is an int for now but may be expanded into a more descriptive set of options later on.
  • private (boolean), which makes it so this item only shows to the person who added it to the list, so something like “cake for surprise party” isn’t visible to everyone else who has access to this list.
  • “okay to substitute” (boolean), which flags whether the shopper should attempt to buy a different item instead
  • a list of suitable substitutes, as well as an option for “any” (or some other way to communicate “not picky”)
  • urgent (boolean) for marking items as high priority
  • an item belongs to one user-defined category
  • an item belongs to at least one list

Most of this data is optional. The intention is that the user will create an item quickly then refine it over time, especially for recurring items. The substitution information is to help people in the same household communicate brand preferences to whoever is actually at the store to help cut down on real-time texting and confirming of item choices.

Now we have this:

Passing the item data into existing_item.dart is handled around line 97 in item_list.dart:

gotoExistingItemManagement(context) {
  if (listType == 'shopping list') {
    print("opening page: manage existing shopping list");
  ...
  } else if (listType == 'item') {
    Navigator.pushNamed(context, ExistingItem.routeName, arguments: ExistingItemArguments(item));
  } ...
}

ExistingItem is stateful, so the initial code structure should look familiar by now:

existing_item.dart

class ExistingItemArguments {
  final Item item;
  ExistingItemArguments(this.item);
}

class ExistingItem extends StatefulWidget {
  static const routeName = '/existingItem';
  ExistingItem({Key key});

  @override
  _ExistingItemState createState() => _ExistingItemState();
}

class _ExistingItemState extends State<ExistingItem> {
...
}

The tricky part was getting this stateful widget to use the item parameters that were passed to it.

This page is for editing an existing item, so I want to populate the form fields with its existing data, but I also want the user to be able to edit that data.

The first thing I added was “local” copies of the variables that will come in with the “args”.

class _ExistingItemState extends State<ExistingItem> {

  String _name = 'ERROR: ARGS NOT LOADED!';
  int _quantity = 0;
  bool _subsOk = false;
  bool _urgent = false;
  bool _private = false;
  ExistingItemArguments args;

Then, inside the Widget build(...) stuff, if args is null (they haven’t been loaded in yet), set args equal to the args that came in from the route.

@override
Widget build(BuildContext context) {
  if (args == null) {
    args = ModalRoute.of(context).settings.arguments;
    _setStartingValues(args.item);
  }

(See the official docs for more info on ModalRoute.of(context).settings.arguments)

Now we have a bunch of pages that are wired together, passing and displaying mock data:

Here is the pull request that finishes up this work by making the “edit existing item details” page elements functional.

You may also like to browse the repo at this point in development.

Up next in Part 4: refining the mock data, building components for repeated code, and improving the ways data gets passed around.

Building a Flutter app, part 2: boilerplate, reusable components, and the main screen UI

Welcome to part 2 of my Flutter App development journal. In Part 1, I set up Flutter on my MacBook and documented some of the hurdles I encountered. In Part 2, I am going to build the main screen UI and many of the app’s reusable components.

In this post: Putting together the main screen’s UI using ordinary Flutter widgets. Features include: scrollable screen with multiple lists and headers, models for mock data, and displaying mock data.

To view all of the code associated with this step on GitHub, click here.

Creating the boilerplate Flutter app

Running the flutter create app_name command creates a folder with the app_name you give it and fill it with some starter code (called “boilerplate”) that you’ll either use or delete as you build your own app.

I like to build from the boilerplate app code you get from running flutter create, so I started with that.

Where to run flutter create

Run flutter create app_name in the folder where you want your codebase to be created. In other words, if you want it to be in /projects, go to projects and run it inside projects.

Note: Don’t do what I initially did, which was create the project folder, enter it, and run flutter create inside it. That will give you a folder structure like this: /projects/app_name/app_name/ and I assume you don’t want that.

I keep all of my projects inside a folder named projects, so I created my “Grocery Go” app from that directory:

cd projects
flutter create grocery_go 

That will give you /projects/grocery_go/

Planning the main screen UI

My favorite mockup tools all seem to have gone to a subscription model, so I’m just gonna sketch my UI plans real quick on this notecard here…

High tech!

The “goals” for this UI are:

  • Two lists of items, one to represent “Shopping Lists” and one to represent “Stores”.
  • Each list has a header
  • Neither list should be scrollable on its own (no “scrolling within scrolling”), but the whole screen should scroll up/down

Creating the “Header” reusable component

This is the part that displays the section title, like “Shopping Lists” or “Stores”, in the scrolling list on the main page.

I know I’m going to use that “Header” bar at least twice, so I made it its own component. I also created a /components directory to hold MainScreenListHeader.

(Perhaps this might be better termed a “widget”, but “component” was ingrained during my years of working as a web developer and it’s the first word I think of when describing reusable pieces of UI).

Anyway, I now have:

grocery_go/lib/components/main_screen_list_header.dart

I use underscores in the filenames and UpperCamelCasing for the class names inside, which is Dart convention.

Inside the file, I create a class called MainScreenListHeader that extends StatelessWidget and give it a constructor that takes this.text, which will serve as the title displayed in the header bar. This component returns a Container with a color, height, and a child Text widget.

import 'package:flutter/material.dart';

class MainScreenListHeader extends StatelessWidget {
  final String text;

  MainScreenListHeader({Key key, @required this.text});

  @override
  Widget build(BuildContext context) {
    return Container(
        color: Colors.blue[800],
        height: 30.0,
        child: Center(
            child: Text(text,
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 18,
                )
            )
        )
    );
  }
}

I wanted to pick a simple example for the first component, but the next one will be more complicated.

Creating the reusable “ListView” component

The list that contains individual shopping lists (such as “Groceries”, “House stuff”, etc.) and the list of the user’s stores (“Safeway”, “Fred Meyer”, etc.) appear in vertical lists the user can scroll up and down through.

Stores also display their address or location so the user can distinguish the Safeway near their office from the Safeway near their home.

Since I don’t know how many Shopping Lists or Stores a user might have saved, ListView builder seemed like a good widget to start with. I also put this in its own component from the beginning, which I named

grocery_go/lib/components/main_screen_list.dart

import 'package:flutter/material.dart';
import 'package:grocery_go/models/shopping_list.dart';

class MainScreenList extends StatelessWidget {

  final list;

  MainScreenList({Key key, @required this.list});

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
        shrinkWrap: true, // gives it a size
        primary: false, // so it doesn't scroll
        itemCount: list.length,
        itemBuilder: (BuildContext context, int index) {
          return ListTile(
            title: Text(list[index].name),
            subtitle: Text(list[index] is ShoppingList ? list.length.toString() + ' items' : list[index].address),
          );
        }
    );
  }
}

Now all I need to do is pass in list objects from main.dart, which are declared as two separate lists of ShoppingList and Store objects.

I created two model files:

grocery_go/lib/models/shopping_list.dart

class ShoppingList {
final String name;

ShoppingList({this.name});
}

and

grocery_go/lib/models/store.dart

class Store {
  final String name;
  final String address;

  Store({this.name, this.address});
}

The last piece of the puzzle was to import the components and models in main.dart and change the body: property of the Scaffold that gets returned to use a LayoutBuilder that returns a SingleChildScrollView of a Column with children that are instances of my MainScreenListHeader and MainScreenList (whew – I’ll explain more after the code).

import 'package:flutter/material.dart';

import './components/main_screen_list.dart';
import './components/main_screen_list_header.dart';

import './models/shopping_list.dart';
import './models/store.dart';

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

class MyApp extends StatelessWidget {

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Grocery Go!'),
);
}
}

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

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

// Some placeholder data just so we can see things working
final List<ShoppingList> shoppingLists = [
ShoppingList(name: "Groceries"),
ShoppingList(name: "House stuff"),
];

final List<Store> stores = [
Store(name: "Safeway", address: "Juanita"),
Store(name: "Safeway", address: "Bellevue"),
Store(name: "Home Depot", address: "Bellevue"),
Store(name: "Fred Meyer", address: "Kirkland"),
Store(name: "Fred Meyer", address: "Bellevue"),
Store(name: "Fred Meyer", address: "Ellensburg")
];

@override
Widget build(BuildContext context) {

const headerShoppingLists = "Shopping Lists";
const headerStores = "Stores";

return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints viewportConstraints) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
MainScreenListHeader(text: headerShoppingLists),
MainScreenList(list: shoppingLists),
MainScreenListHeader(text: headerStores),
MainScreenList(list: stores),
],
),
);
}),
);
}
}

These are the major widgets used in this layout and why I picked them:

Layout Builder – this widget sizes itself to its children at runtime (among other things, but that’s why I picked it). Since I don’t know how many Shopping Lists or Stores the user has, I need to build the layout with that in mind.

SingleChildScrollView – This is a container that scrolls, and you can fill it with whatever you want. I have multiple ListViews inside it but I want them to scroll as a single unit, so I’m going to turn off their own independent scrolling ability and let the SingleChildScrollView handle it instead.

Column – I want my elements to appear in a vertical column, one after another: header, list, header, list, and a Column makes that easy. I had to set mainAxisSize: mainAxisSize.min to get it to work with everything else I have going on.

ListView.builder – I don’t know how many Stores or Shopping lists will be displayed, so I used ListView.builder. There were two important properties I had to set on it:

shrinkWrap: true – I had some trouble getting my ListView to work inside a Column at first. I kept getting a 'hasSize': is not true error. I found this StackOverflow post helpful, particularly Aziza’s reply.

primary: false – setting primary to false disables scrolling for this particular list. I want the SingleChildScrollView to handle the scrolling instead.

Here it is in Simulator: two ListViews inside a Column inside a SingleChildScrollView so they move as a unit. Hooray!

GitHub repo at this step.

Adding ‘add new…’ buttons to each list

The next thing this UI needs is a “Add new list…” and “Add new store…” button at the end of each list. Tapping these buttons will take the user to a page where they can create a list or a store.

I changed the MainScreenList widget to take a listType string, which I will use on the “add new” button itself:

class MainScreenList extends StatelessWidget {

  final list;
  final String listType;

  MainScreenList({Key key, @required this.list, @required this.listType});

...

Also in main_screen_list.dart, I changed itemCount to be the list length plus one more, so that last iteration can be used to draw the button at the end of the list, and the “listType” string that got passed in will used in the title, like so:

...
itemCount: list.length + 1,
itemBuilder: (BuildContext context, int index) {
  if (index == list.length) {
    return ListTile(
        title: Text("Add new " +  listType  + "...")
    );
... 

Back in main.dart, I now pass “shopping list” or “store” to MainScreenList.

children: <Widget>[
MainScreenListHeader(text: headerShoppingLists),
MainScreenList(list: shoppingLists, listType: "shopping list"),
MainScreenListHeader(text: headerStores),
MainScreenList(list: stores, listType: "store"),

Now there is a “Add new shopping list…” string at the end of the shopping lists:

And there is one at the end of the Stores list, too:

You can see these changes in this commit.

And that’s it for Part 2! Here’s a summary of what we did:

  • Drew a quick mockup of what the main screen of the app should look like
  • Ran flutter create to start the project
  • Built some reusable components that are used to build a list of Shopping Lists and a list of Stores
  • Populated those components with mock data
  • Made the main page scroll as one large list

Up next in Part 3: adding more screens and navigating between them.

Building a Flutter app, part 1: installation and setup

One of my favorite classes in OSU’s online CS degree program was CS492 Mobile Development. I’ve always wanted to make my own iOS/Android app and CS492 gave me enough of a foundation to attempt it.

In this multi-part “journal” series, I’ll share all the major steps and decisions I made as I developed a Flutter app.

In this post: some set-up hurdles I encountered on my MacBook. If you want to skip setup and go to the beginning of the code, go ahead to part 2.

Initial Flutter set up

For the most part, setting up Flutter was easy – I followed the official docs and it took me about 45 minutes to get the sample app running. (I’m on a new MacBook running macOS Catalina 10.15.3.)

I ran into a few hurdles, though, which I’ve documented here for other Mac users and for my future self (because I will inevitably go through this again someday).

Adding Flutter to the $PATH on a Mac

I followed the doc’s example of creating a “development” folder in my Users/mandi directory.

The full directory structure surrounding my “flutter” development directory.

I wanted to follow the Flutter documentation example for adding Flutter to my path permanently, but I didn’t know how to find the PATH_TO_FLUTTER_GIT_DIRECTORY on my machine. I know I made a ‘development’ folder, but I don’t know what’s “before” it in its file path.

As it turns out, there’s a handy trick you can do using Finder and a Terminal window to get the full path to a directory or file.

Dragging the ‘development’ folder into Terminal reveals its full path.

Dragging the “development” folder into a Terminal window gives the complete path you need for setting the $PATH.

That was what I needed to run:

export PATH="$PATH:/Users/mandi/development/flutter/bin"
source ~/.zshrc

Unfortunately, this didn’t “stick” on my machine. It worked for the Terminal window I was in, but as soon as I opened a new Terminal window the “flutter” command was no longer found.

It’s still in there if I echo $PATH, so I’m not sure what’s wrong here. But I’m not the only person who ran into this issue, as evidenced by this Stack Overflow post. Kaushik Bharadwaj’s answer explains how to add it to the path file permanently.

In a Terminal window, enter:

sudo nano /etc/paths

Add the full path to the flutter/bin folder [this is what mine looks like, yours may vary] to this file and save it:

/Users/YOURNAMEHERE/development/flutter/bin

On my machine, the paths file looks like this:

Now the “flutter” command persists through Terminal window closures, reboots, etc.

IDE for Flutter

There are a bunch of choices but I’ve only used Android Studio for Flutter development so far. I’m happy with it and it’s pretty good at formatting Flutter’s copious amounts of nested code.

If you choose Android Studio, beware that the default project setting might be “Android”. You can change that by clicking the little Project dropdown in the upper left corner and selecting “Project” instead of “Android”.

That should give you a nice view of the project structure, like so:

This step is unnecessary but it’ll get rid of the weird “Java” folder that isn’t represented in the project structure if you view it in Terminal or Finder and it’ll make your environment look like the one I’ll be sharing in screenshots throughout this journal.

Emulators

There are a few to pick from. I’m on a MacBook and I find that the iOS Simulator runs way better than the Android simulator. In Android Studio, they appear here in the top bar:

On a Mac it “just works” with the built-in Simulator (which is iOS-specific). I’m not sure what’s involved in other dev environments but I’ll come back to this when I try to build for Android later.

Final checks with Flutter Doctor

In Terminal, using flutter doctor will show the status of your flutter setup. You don’t actually need a checkmark next to everything in this list to start coding, but when it’s time to push your app onto a device you’ll want to clear out the “!” and “x” marks where appropriate.

Here is my flutter doctor output at the time of this writing. I’ll come back to these later when it’s time to test on an actual device – this is good enough to get started.

With the project set up, it’s time to move on to Part 2 where I’ll show you how I built the main page’s UI.

React – Fixing a flickering video that gets redrawn every time something on the page changes

In this post: Fixing a video preview that gets restarted and redrawn every time React redraws the page.

Key words: React, HTML video flicker, DOM redraw React, useMemo

First, an example of the problem:

(This is painful to look at. That’s why we’re going to fix it!)

This is a form that allows the user to upload a video and add a message. But every keystroke was causing the video preview to redraw!

Yikes indeed.

Originally, the <video> tag was part of the React component’s return, like this. On every state change (in this case, the contents of the text area changing), the whole DOM was getting redrawn, video and all.

return (
  ...
  // lots of stuff omitted for brevity 
  ...
  {fileType === 'video' &&
    <Row justify="center">
      <video src={videoURL}
        className="photo"
        preload="auto"
        controls
        style={{height: '95%', width: '95%'}}
      />
    </Row>
  }
  ...
)

I started Googling things like “React video flickers when updating a form” and “React redraws video preview too often” and eventually landed on this helpful tutorial, Getting the heck out of React, which helped me understand the problem better, but the author’s examples were written for an older (2018) version of React and I got lost trying to adapt them to my project. (My React journey only began a few months ago and it seems a lot has changed since the “older” tutorials were written).

I knew there had to be something, but I couldn’t find it by describing my problem to Google, so I explained it to a friend who has more React experience and he pointed me at the useMemo hook, which the docs describe as follows:

Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

reactjs.org

Okay, that sounded promising. But I still didn’t know how to use it, and the method signature example just exists in a vacuum.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

The method signature sets it to the value of a const, but I didn’t know where to put this const or what to have it return, so I went looking for more examples of useMemo.

This tutorial (written in April 2020) was fresh and full of examples. My key takeaways about useMemo were:

  • useMemo doesn’t have to return a function that takes parameters (maybe I’m just a newbie, but seeing the (a, b) in the signature made me think it was going to act like a .sort() callback)
  • useMemo can return JSX instead of a function (JSX being HTML created by JavaScript or TypeScript), which means it can also return React components
  • useMemo has a dependency array like useEffect, and it will run the function it’s attached to if something in that dependency array changes
  • If the dependency array is present but empty, it will compute once on page load and then never again (which isn’t what I want, since the video preview is meant to be set/updated by the user)
  • If there is no dependency argument at all (no array), it will compute every render (which would defeat the purpose of useMemo, so I knew my use case would have to including something in the dependency array)

For my use case, I created a new const, renderVideo, that uses useMemo and placed it above the return (...) but still within the component that makes up the page. The function called by useMemo returns the markup (containing the <video /> tag) that should only redraw when selectedFile changes. Then, where the <video /> markup used to be, I replaced it with a call to renderVideo instead.

The code explains it best:

    const renderVideo = useMemo(() => (
        <Row justify="center">
            <video src={getImageAsUrl()}
                className="photo"
                preload="auto"
                controls
                style={{height: '95%', width: '95%'}}
            />
        </Row>
    ), [selectedFile])

    return (
      ...
      {fileType === 'video' &&
        renderVideo
      }
      ...
    )

(Note: no parenthesis or curly brackets are needed around renderVideo)

It was that simple!

I think I ran in circles for a little while on this one because:

  1. I didn’t know about useMemo, and once I did,
  2. I didn’t realize useMemo could be used for just returning some boring old JSX (HTML markup, basically) that simply has to remain static until a state variable changes – no comparisons or logic calculations needed

Anyway, I hope this helps someone else fix a flickering video or avoid an expensive recalculation on every React redraw!

Additional notes

Notes and observations that didn’t fit elsewhere…

Note #1: the reference to the const can’t be wrapped in any other HTML tags

I had some initial trouble with this technique because I had additional HTML tags around renderVideo. The call to the useMemo const has to be the only thing after the &&.

For example, it does NOT work if you do this:

  // bad idea, does not work!

  {fileType === 'video' &&
    <div className="videoStyling">
      renderVideo
    </div>
  }

Note #2: Using this technique with a component. I later refactored the video preview stuff into its own component, which looks like this:

VideoPreview.tsx

import React from 'react';
import Row from 'shared/components/Row/Row';

interface IVideoPreview {
  videoSrc: string
}

const VideoPreview: React.FC<IVideoPreview> = ({videoSrc}) => {
  return (
    <Row justify="center">
        <video src={videoSrc}
            className="photo"
            preload="auto"
            controls
            style={{height: '95%', width: '95%'}}
        />
    </Row>
  )
}

export default VideoPreview;

I updated const renderVideo to use my new <VideoPreview /> component instead:

    const renderVideo = useMemo(() => (
        <VideoPreview videoSrc={getImageAsUrl()}/>
    ), [selectedFile])

React – replacing an asynchronous .forEach() with for…of (and why it’s better that way)

In this post: My investigation into why ESLint didn’t like my .forEach loop and why for...of is the better choice for an asynchronous loop.

Key words: React, TypeScript, loops, JavaScript array iterators, forEach, for let, asynchronous

So there I was, .forEach-ing through some post replies when I noticed the code was triggering an ES-Lint error, no-loop-func:

Line 73:28: Function declared in a loop contains unsafe references to variable(s) 'repliesTotal' no-loop-func

My code seemed to work fine as it was, though.

const processUnreadReplies = async (userPosts: Post[]) => {
  let repliesTotal = 0;
  for await (let post of userPosts) {
    const replyArray: Array<Reply> = await getRepliesToPost(post.pid);
      replyArray.forEach((reply: any) => {
        if (!reply.read) {
          post.unreadReplyCount = (post.unreadReplyCount ?? 0) + 1;
          repliesTotal++;
        }
      });
  };
  setUnreadRepliesTotal(repliesTotal);
}

In English, this is what this code is doing:

  1. It gets all of the user’s posts as userPosts (and they are of type Post)
  2. It loops through each post in userPosts, getting that post’s replies as replyArray
  3. For each reply in replyArray, it checks each one to see if it is read or not by waiting on an asychronous function call
  4. If the reply is not read (reply.read is false), then it increases the post’s unreadReplyCount
  5. When it’s done with the loop, it sets a state variable called unreadRepliesTotal to the total it tallied up during the loop

ES-Lint’s documentation for no-loop-func was informative, but the examples didn’t make it clear enough to me what I had done wrong. Their “don’t do this” examples were "let i = 0, i < n; i++" and do ... while style loops, and I had a .forEach. They also didn’t have anything on the topic of asynchronous loops.

(Could my code be so awful that they didn’t even consider including it as a “do not do this” example? :D )

I decided to investigate.

First, I had to rethink my assumption that .forEach was the preferred ES6 style – maybe it wasn’t always the case, and maybe my case was one of them.

Two helpful (ie: plain English) posts I read while researching this problem:

For...in iterates through the enumerable properties of an object or array, which means it steps through all the X in object[X] or array[X]. You can still use it to access the elements of an array like so:

for (const idx in cars) {
  console.log(cars[idx]);
}

But that a more roundabout way of accessing the array data, and I soon found a more direct approach:

For...of was the next thing I tried and it worked just as well as my .forEach, but the linter liked it better.

const processUnreadReplies = async (userPosts: Post[]) => {
  let repliesTotal = 0;
  for await (let post of userPosts) {
    const replyArray: Array<Reply> = await getRepliesToPost(post.pid);
      for (let reply of replyArray) {
        if (!reply.read) {
          post.unreadReplyCount = (post.unreadReplyCount ?? 0) + 1;
          repliesTotal++;
        }
      };
  };
  setUnreadRepliesTotal(repliesTotal);
}

But why? I went back to the MDN docs and discovered an important tidbit I overlooked earlier:

forEach expects a synchronous function

forEach does not wait for promises. Kindly make sure you are aware of the implications while using promises (or async functions) as forEach callback. 

from the MDN docs on forEach

As far as ESLint could tell from looking at the code, every loop run by .forEach was returning void. Furthermore, using await does not pause the .forEach (I didn’t expect it to pause it, and I don’t need the previous iteration’s result for the next one, but I wanted to make note of that important distinction anyway).

In any case, this seemed like one of those times where the thing appeared to be working to the user, but could be done better and for...of was the preferred approach here. (There are plenty of opinions and debates about this, though.)

In summary

  • Use for...of for asynchronous loops in JavaScript/TypeScript
  • let creates a block-scoped variable, so in the case of my code the let is creating a new “reply” instance for each iteration of the for...of and each “reply” will be an individual reply from the array
  • .forEach() does not wait for asynchronous code to complete, which may not be a problem in your specific use case but ESLint can’t tell that so it flags it as a potential problem
  • await suspends the current function until it is resolved
  • for...of creates an individual function call for each loop, whereas .forEach() counts as one function call for all iterations

So there we have it – a better, ESLint-approved, way of writing an asynchronous loop in TypeScript/JavaScript.

Related

React – Using the useMediaQuery hook to make the number of columns in a Material UI GridList responsive

In this short tutorial: How I used React’s useMediaQuery hook to make the number of columns in a Material GridList responsive.

Let’s say you’re using Material UI’s GridList, but you want the number of columns it uses to change depending on how wide the user’s screen is, like this:

Poking around the API reveals that you can set the number of columns by setting the col property to a value, but it doesn’t say how to make it responsive.

If you Google this problem, you might come across this StackOverflow post where the top-rated reply suggests using withWidth, but the React docs have widthWidth tagged as deprecated.

Instead, they point you at the useMediaQuery hook, which is what my example will use.

Being somewhat new to React hooks, my first instinct was to put this logic into a function but React didn’t like that. The useMediaQuery hook has to sit inside the functional component, and not inside a useEffect, or a function, or any of the other things I thought I should reach for. Also, it returns a boolean, so it’s better to think of it as setting a flag that you then check somewhere else.

Eventually, I arrived at this approach which sets a series of flags to true/false and then checks them to determine how many columns to use:

import React from 'react';
import { useTheme } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';

interface IInbox {
  posts: Array<Post>; // array of type "Post"
}

const Inbox: React.FC<IInbox> = ({ posts }) => {

  const theme = useTheme();

  const screenExtraLarge = useMediaQuery(theme.breakpoints.only('xl'));
  const screenLarge = useMediaQuery(theme.breakpoints.only('lg'));
  const screenMedium = useMediaQuery(theme.breakpoints.only('md'));
  const screenSmall = useMediaQuery(theme.breakpoints.only('sm'));
  const screenExtraSmall = useMediaQuery(theme.breakpoints.only('xs'));
  const screenNarrow = useMediaQuery('(max-width:340px)');

  const getScreenWidth = () => {
    if (screenExtraLarge) {
      return 6;
    } else if (screenNarrow) {
      return 1;
    } else if (screenLarge) {
      return 5;
    } else if (screenMedium) {
      return 4;
    } else if (screenSmall) {
      return 3;
    } else if (screenExtraSmall) {
      return 2;
    } else {
      return 3;
    }
  }

  // note: I skipped some of the structural elements of my return statement to focus on just what's relevant to this example

  return (
    <>
    {posts.length === 0 &&
       <span>No posts!</span>
    }

    {posts.length > 0 &&
       <GridList 
         cols={getScreenWidth()}
         spacing={2}
         id="grid-list-inbox"
       >
       {posts.map((post, index: number) => (
         <GridListTile
           id={`grid-list-tile-${post.pid}`}
           className="inboxLetter"
           key={post.pid}
           onClick={() => pressEnvelope(post)}
           rows={1.25} >
             <img src="path/to/image.png"/>
             <GridListTileBar
               title={"From: " + post.from}
               className="inboxTitle"
             </GridListTile>
         ))} // closes .map 
         </GridList>
    }
    </>
  )
}


export default Inbox;

As the screen is resized, the col property runs the method and gets the correct number of columns to use.

If I find a more elegant way to do this I’ll update this post, but in the meantime, I hope it helps someone else on their journey with React useMediaQuery!

OSU eCampus CS492 Mobile Development review and recap

This post is part of an ongoing series recapping my experience in Oregon State University’s eCampus (online) post-baccalaureate Computer Science degree program. You can learn more about the program here.

Six-word summary: BEST COURSE IN THE PROGRAM 🥇

This class rules. Yong Bakos is a fantastic lecturer and course designer. CS 492 is the missing link between all the small-scope student projects you’ve done so far and the much larger, much more complex projects you’ll work on as a professional.

They call it “mobile development” because you’ll do this class’s work in Dart and Flutter, and see your work on a mobile device, but this class grows a ton of skills that are important no matter what you end up working on.

My advice: take this class even if you have no interest in mobile development.

CS492 Review

CS492 is what every class in this program should’ve been.

Every week introduces a batch of lectures that are actually enjoyable and useful. Follow along with the examples and what you build is directly useful for the projects you turn in every 2 weeks.

This class covers a huge range of topics, including:

  • Setting up a development and build environment above and beyond just running a file from the command line
  • Coding style, with concrete examples of what to do and what not to do
  • Working with an SDK
  • Reading and using API docs
  • Refactoring, “code smells”, best practices for structuring methods and projects as a whole
  • Design patterns that you can take to any language
  • Best practices (guards, import order, function design)
  • Actually building something!

I took CS492 the first time it was offered so I have to admit, I was pretty scared. Some OSU classes have taught me to temper my expectations and I was bracing for an uneven workload, messy, unfinished course content, unclear expectations, long-winded 50 minute lectures full of “ums” and mistakes that go uncaught, etc.

I mean, even the courses that have been running for longer than I’ve been a student suffer from all of these problems and they’ve had ample time to clean up, so why wouldn’t a brand new one be more of the same?

None of these things were a problem in CS492.

This class was organized, consistent, and predictable. The lectures are high-res, easy to follow, and enjoyable. You follow along with the professor as he builds a project from scratch. Each lecture is just 4-8 minutes long and covers a specific topic, so you don’t waste time scrubbing through a marathon hour-long lecture for the part you need. Early lectures are followed by a coding exercise built right into Canvas where you can complete a quick ungraded exercise to reinforce what you just learned.

The project grading rubrics were clear. The weekly discussions felt meaningful (for the most part), and I found myself skimming what other people contributed just to see what I could learn from them.

The projects felt like the start of something “real”, and I enjoyed the opportunity to use all the pieces of the framework. Overall, I learned a ton in this class and it was almost entirely smooth sailing.

The only things I didn’t like were:

  1. Initially, new modules unlocked on Mondays instead of Sundays (like almost all the courses do), making it hard to get a running start during the weekend. Many students complained and the instructor running the course (not the same person who recorded the lectures) started unlocking them Friday at midnight. This kind of responsiveness is fantastic. The early-unlocks ended around week 7, when I think the instructor was still editing the pre-recorded lectures. If they improve one thing in this class, I hope they make the next week’s content unlock earlier than Monday.
  2. Initially, the correct answers to the weekly quizzes remained hidden forever. During Week 3, the instructor changed a Canvas setting to release the quiz answers a day after the quizzes were due. Quizzes that just tell you what you got without telling you the right answers so you can learn from them are useless, so I’m glad the instructor listened to reason on this one, too.

I liked that the instructor (who was different from the guy who recorded the lectures) was responsive to student complaints about these things.

I also liked how this class hit the ground running. In the first week you get set up with Flutter and follow a well put-together tutorial that guides you through making a simple app. Wow, a simple app in the first week! The following weeks continue to add complexity, and the end result is a course with a very satisfying “deep dive” feel to it.

CS492 course structure

  • 10 weeks
  • There’s a book but I didn’t get it and managed an A without it
  • Weekly quizzes (took me about an hour apiece, they’re not trivial and there is some wording I would have changed here or there, but they aren’t horrible)
  • Class-wide Canvas-based discussion to post to by Wednesday of each week (the topic changes each week)
  • Response to someone else’s discussion post due by Sunday of each week (the response was usually fairly detailed, ie: write a rebuttal, provide sources, implement a some code to create what the OP was describing, etc.)
  • New material unlocks every Monday – later changed to early Saturday morning, later back to Monday much to everyone’s chagrin in the final weeks
  • 4 projects (the 3rd one was the hardest/most time-consuming, but they were all a fair bit of work)
  • Project submissions are both your code and a 90-120 second video (screen recording) of your project as you quickly step through it to demonstrate it meets the requirements
  • Proctored final exam worth 35% of the grade – note: in my run of the class, the final was changed to unproctored near the end of the quarter due to a lack of testing sites because of COVID-19

I cannot say enough good things about the quality of teaching in CS492.

Take this class. Take this class. Take this class.

Topics covered

  • Many Flutter-specific conventions like Widgets that, while they may seem specific to Flutter, have analogs to other libraries like React
  • Structuring and modularizing a project
  • Asynchronous programming (async/await)
  • Saving data to on-device storage
  • Saving user preferences
  • Hooking up to an external database with Firebase
  • Uploading and retrieving photos to/from cloud storage with Firebase
  • Integration testing in Flutter (lightly covered)
  • Debugging

Time commitment

I took CS492 alone, which gave me time to really dig into everything. I might’ve spent a little less time on it had I paired it with a second class, but every week was very consistent and worked out to about:

  • 3-4 hours watching the lectures, reading the accompanying explanations, and following along with the code (the lectures themselves are maybe 45-70 minutes of content each week but I paused a lot to follow along)
  • 2 hours on the weekly quiz
  • 1-2 hours on my weekly discussion post
  • 1-2 hours on my weekly discussion post reply
  • About 8-10 hours on the current project

That’s about 15-20 hours a week depending on the week. The weeks where projects were due tended to be closer to the “20” end of that spectrum than weeks where projects were introduced. This was mostly because the second week of each project tended to contain the lectures pertaining to the more challenging aspects of the project.

The projects spanned 2 weeks, but lectures unlock weekly, so you could only get through half the project the first week because the second half of lectures wouldn’t unlock until the next week.

For example:

  • Week A – Project assigned, needs Lectures from Week A and Week B to complete, only Lectures A are made available
  • Week B – Lectures B become available, project due at the end of the week

This got irritating at times. At the end of every “Week A” it felt like sitting in a holding pattern all weekend waiting for the second batch of lectures to unlock so I could start the second half of the project, but I always had enough to for the first part of the project and could fill the weekend.

My advice to other students in this holding pattern is to do absolutely everything you can do for the first half of the project in the first week. Leave nothing that could be done in “Week A” for “Week B”, and you’ll be fine.

Recording projects

This was my first class that asked for screen recordings and I was pretty intimidated at first. I do not like being recorded and tend to make a lot of mistakes, get really self-conscious, freeze up, etc. However, much to my relief, you don’t have to record yourself or any kind of voice-over, just your screen.

You just step through the script they provide you and after a few run-throughs you’ll nail it all in one take. I found it helpful to create a doc with an extremely detailed, click by click script to follow and have someone else read it to me. That way I didn’t have to switch between the script and my active recording, and didn’t lose my place so much.

On a Mac, it’s easy to make a screen recording using Quicktime. Just search for Quicktime Player, cancel the default dialog it opens, then right click it to start a new screen recording.

My recordings were usually around 80MB but Canvas accepted them just fine.

CS492’s lectures are fantastic

In general, the lectures in this online CS program are low quality. Many classes have lectures that are poorly paced, unedited, boring, or only loosely related to the assignment you’ll be doing that week.

CS492’s lectures are none of that. They are short, punchy, and purposeful. They’re organized by topic and easy to find later when you need to refer back to one.

You follow along with the lecturer as he codes from scratch, explaining every decision he makes. He gives tons of little tips, too (like “build frequently”, “don’t make a temp variable just to return it in the next line”, etc.) and he explains his reasoning as he splits things into methods.

It’s like getting to pair program with the most senior dev on your team. This screencap is from week 2 and while a lot of what he covered was review for me, I didn’t feel like he was wasting my time. I genuinely enjoyed following along with him.

Yes, I could use a second monitor.

Nothing in this class felt irrelevant. The work felt like real work (like the kind of work you’ll do at a job once you graduate from OSU). Week 2 was the only week that felt like a bit of a rehash (it covers starting a project, wiring up inputs, organizing things into functions, etc.) but it was a good review and it didn’t take up too much time.

(This process of breaking a program down into pieces, setting up the prints, the inputs, breaking it into modules, and reasoning through the project’s could be moved to CS162 where everyone will see it and learn from it early on.)

Whoa, Canvas has a repl?

I hope Canvas’s support for repl.it is a new feature, otherwise I’m going to be seriously salty that every previous class in this program missed this incredible teaching opportunity.

The repl exercises in CS492 aren’t graded, but they’re broken down into little bite-size pieces that take a few minutes to complete and do a great job of reinforcing the current topic.

Honestly, some of these exercises feel like entry level interview questions. I was grateful for the practice.

Even better (I didn’t discover this until Week 3), you can hook up repl.it to your GitHub account and your edits to the repls will be saved.

They’re public, which isn’t how I usually like to store my student work, but keeping organized backups of my work on these exercises was a big help.

Tips for CS492

Set up the Dart SDK and Flutter ahead of time, or at least early. The setup isn’t necessarily straightforward. In my case, it took about 2-3 hours to work through all the missing components of the install (I needed to upgrade RVM, Ruby, install Cocoapods in the right place, read half a dozen Stack Overflows from people with similar problems, etc.) Some people had a harder time than I did. Starting early is a good idea. You’re good to go when running flutter doctor gives you a clean output of checkmarks.

Get a second monitor (or use a second computer) to display the lectures on their own screen. I made it this far in this CS program before I felt like I needed a second monitor. If you can get two screens, do it – the lectures are much more enjoyable if you aren’t flipping between tabs/programs every few seconds.

Week 3 is also when I got clever and started watching the lectures on my PC and following along on my laptop. Not having to flip back and forth between Chrome and Visual Studio Code saved me a lot of time.

Quiz tips: Unlike some classes, the multiple choice options in this class’s quizzes are not obvious joke answers. Here, the wrong answers are often correct in some way, but don’t answer the question that was asked. Pick the answer that is both correct and most accurately answers the question. It’s sometimes subtle and difficult to spot at a glance. This same advice applies to the final.

Mac/MacBook tip: I worked in Android Studio because I liked it best of the options, but I found the Android emulator to run extremely slowly (like 2 frames a second) while the iOS Simulator ran like butter. You can invoke the iOS Simulator from Android Studio, so don’t let the “Android” label scare you off if you’re on a Mac.

Final exam tips: None of the questions were repeats from the quizzes, but reviewing all the quizzes and the writings that accompanied each lecture was time well-spent in preparation.

Due to COVID-19 shutting down so many test sites at the end of the Winter quarter, the final exam was changed to be unproctored. Future versions of this class might go back to a proctored final, but in my humble opinion, they should leave it unproctored.

The exam was still challenging, but since it was open book it felt like one last opportunity to learn something instead of a punitive experience. The questions aren’t readily Googleable and still require in-depth familiarity with the course’s material. I don’t think having access to the course’s material during the exam raised my final exam grade by more than a point or two, and the timed-but-open-book exam (which was still worth 35% of the final grade) felt more in line with the spirit of the class.

If you do face a proctored version of this final, you can take some comfort in knowing that if you paid attention all quarter long, you’ll do fine. Nothing on the exam was a surprise, and there wasn’t anything painful about it (no tracking memory registers by hand, no memorizing formulas and algorithms, no writing code into a blank Canvas form).

Take this class. Even if you have no interest in being a mobile developer, you’ll benefit from working in a more complex, real-world SDK than what previous classes exposed you to. This class also covers a ton of general-purpose topics that will refine your skills and be useful no matter what you do in your CS career.

If this class is indicative of the quality we can expect at OSU periodically revamps this program’s classes, then I’m very optimistic about the quality of OSU’s online CS program in the coming years. CS492 felt like getting to pair program with a senior engineer for a few hours a week, every single week. Thanks for the great class, Professor Bakos.

CS492 is the highest quality course in the OSU program. It does not run every quarter (I took it in Winter 2020) but it is worth waiting (or retooling your schedule) for.

And that’s it! The only class I have left is Capstone next quarter.

PS: Seriously, take this class.

2019 Learning Recap

As we say goodbye to 2019, I wanted to make a little record of some of the cool things I worked on and learned this year.

I made a web app in React!

I’m very far from a React expert, but I really wanted to at least get my feet wet (kiddie pool, not ready for the ocean yet) in it because React is so popular.

Looking back on this project, I wish I’d documented my trial and error, but it was a lot of trial and error – I’m nowhere near ready to write a tutorial on it.

I used React to build a webapp that checks a blog post you give it for defunct Amazon links:

It scrapes the post you give it for Amazon links and feeds them through Amazon’s advertising API to see if a product comes back or not. If a product does not come back, the link is marked as suspicious in the report it generates. (Some products exist on Amazon.com but are not in their API for some reason – so it does generate some false negatives. Hey, it still beats manually clicking each link.)

It’s live – I deployed it to a free Heroku server (hence the slow startup).

I got my start in web development setting up and running affiliate marketing blogs, and I always thought it’d be neat to have a tool that can check for dead links instead of periodically clicking them all myself.

You have to be an established affiliate with Amazon with at least a small amount of monthly sales to get access to their product API, so I made a video demonstrating how it works for those who don’t have credentials and/or just want to see it in action.

The biggest thing I got out of this project was the experience of revising my initial approach several times. My initial idea was to scrape the blog post for Amazon links, then basically run automated tests on each link to see if an Amazon page loaded and scraping it for data to determine if the product is actually there. Whoops – Amazon does not like being scraped. They direct you towards their API instead, which was a whole new project-within-a-project.

There were many times during this tool’s development where I hit a wall and thought I’d abandon it, only to have an idea later that day for working around whatever limitation I was up against.

I made my first WordPress plugin!

I started using WordPress way back in 2013. I have lots of experience using plugins, but I’ve been frustrated and disappointed in many of them – they just didn’t do quite what I wanted, or they had an overwhelming amount of options that I didn’t need, or they were too expensive.

This year, I decided to stop being intimidated by the idea of building my own and made a very simple (okay, it wasn’t simple, but it looks simple when it’s on the site) “product box” plugin. I documented the entire process in a series of detailed TILCode posts, starting here. I learned a lot from making this, and – most importantly – I realized I’ve finally skilled up enough to take on a novel challenge and carry it through to completion.

You can see its repo here: https://github.com/manderly/amazin-wp-product-box and download it if you want to use it.

I completed five classes towards my CS degree!

I’m almost done! I’m on track to graduate in June of 2020.

I completed nearly 1/3rd of my degree this year. I took:

  • CS340 – Intro to Databases – basically “web dev, part 2” on a somewhat dated tech stack that didn’t go as deep into SQL as I’d hoped it would
  • CS344 – Operating Systems – a challenging class that deepened my understanding of bash, Linux, and sockets through four large projects
  • CS475 – Parallel Programming – a great class on an intimidating topic I’m not sure I would have studied on my own
  • CS372 – Intro to Networking – an incredibly detailed deep-dive into the inner-workings of the Internet, there’s no way I would have slogged through all those calculations on my own
  • CS362 – Software Engineering II – lots of automated testing practice that, unfortunately, left out a lot of standard industry tools and best practices

A common theme to the classes I took this year was “stuff I don’t have much experience with and probably wouldn’t have done on my own”.

One of the most valuable things about this degree program, to me, as someone who already has some professional programming experience, is when it exposes me to things I would not have found (or pushed myself through) on my own.

Some of these classes were very much outside my experience and quite challenging, particularly operating systems and networking. I appreciate learning about these things in a structured academic setting as opposed to on the fly at a job where people are counting on me to already know the basics.

I am still very happy with my choice to attend OSU. I’ve learned so much.

Here’s to a wonderful 2020!