Sunday, May 10, 2020

Learning Flutter - Part 5 - Building out the UI and Incurring Technical Debt

In my last post, I wrote about the Provider class to separate presentation logic from other "business" logic.

My next step was to build out a UI to allow the user to select a vehicle make and model. At this point, I was only building broad categories (car, truck, motorcycle) and got familiar with gridlines and the various properties of common widgets to build UI:



Building the UI is where hot reload, Dart DevTools, and the screen painting functions (see the 2nd image above) really help. It is really quite quick to change a parameter and see the UI change almost immediately. It took me longer to look for data on vehicles (images, fuel efficiency, wear-and-tear cost estimates) - for the v1 it may be that I only get some basic 'generic' vehicle types like the above.

I also started playing with some icon designs - a few iterations thus far but nothing that looks at all good:




In this process, I've built up some technical debt:

  • Magic numbers/strings within the code (though for UI/layout type items, some of these might be acceptable, for text strings they definitely are not); separating text strings is important both for maintainability and for internationalization
  • Repetition of certain aspects of code (e.g. purple background colours); repetition makes code more difficult to change, for example when adding support for Dark Mode
  • No unit tests or project tests
That said, given the project is quite young, having a bit of technical debt is not terrible and not surprising. Particularly as I'm just starting to work on the UI code, my process is to get it working then get it clean. Some of the technical debt is really just unimplemented features (e.g. the code does not yet support switching between metric and imperial units).

Avoiding too much technical debt is important - if you don't then you'll end up with something that isn't robust and that you can't change without spending (wasting) too much time cleaning up impacts of changes where the impacts really should be isolated.

Looking at the plan, I've made some progress - still lots to do and to learn!
  • Selecting vehicles that are not 'average' which will require:
    • UI to select a vehicle make and model [DONE]
    • Vehicle data (icon image, fuel efficiency, wear-and-tear cost estimates) [STARTED ON BASIC 'GENERIC' TYPES]
    • Code within the model to use the parameters
  • Selecting and saving parameters such as metric vs imperial
    • UI to select parameters
    • Ability to save parameters between runs of the app
    • Code within the model to respond to parameters (e.g. by displaying metric)
    • Ability to pull defaults based on location (for imperial/metric, local fuel prices)
  • Support for 'trips' 
    • Building a UI to start and stop trips
    • Updating the model to enable starting and stopping the GPS
    • Persisting trips to the phone 
    • Viewing past trip data and total data
  • Support for user alerts (as the app will normally be in the background)
    • When the user has forgotten to turn the trip recording off (could be fancy AI but likely if the speed has just been low enough for long enough)
    • Alerts based on cost parameters (e.g. every dollar of fuel cost or after 25 liters -- when you might need to refuel)
  • Changes to the UI to make it look nicer and to adapt to various sized phones
  • Unit tests to help keep the project relatively bug free


Sunday, May 3, 2020

Learning Flutter - Part 4 - State Management and Provider

In my last post, I explored how to pull real-time GPS locations using streams in a simple app.

My next step was to pull the GPS code into the prototype I'd made in part two of this series. To do so required understanding how to manage state within Flutter apps. Fortunately, Flutter.dev's section on state management is pretty well explained and the talk from Google I /O '19 on Pragmatic State Management in Flutter was helpful as well. As both of them recommended the Provider approach, that's the approach I chose.

Implementing Provider required (in addition to adding Provider as an import):

  • Building a class to hold the GPS data and route data separate from the UI as a class that extends ChangeNotifier
  • Putting a ChangeNotifierProvider at the top of the widget tree in main.dart
  • Adding a Consumer in the builder that builds the distance display in the UI
None of the points above were too difficult. 



Once I'd connected the pieces, I needed to link up the points to calculate distances (which worked but only in production - not in testing - see the next paragraph). Once I could calculate distances, I needed to hard-code some parameters into the model to calculate fuel use, fuel cost, wear and tear cost, and carbon footprint. 


In building the model class (that held the GPS and route data) I tried to build a unit test. Unfortunately that did not work so well as the locationprovider package uses platform implementation to implement the distanceBetween method. So my unit tests were failing given that when I called them, the platform GPS had not been initialized. The code itself worked but the test cases did not. I did learn something about how the unit test framework in Flutter works though, which was useful.

I also added a FloatingActionButton to reset the trip to zero. What the project looks like thus far is below:



At this point, I'd say that I have an alpha version. The core functionality is there but it needs a lot of work and polish to enable use in a variety of circumstances (e.g. by selecting vehicles that are not average, to automatically decide on metric vs imperial and let the user change that, to allow for stopping and starting separate trips, and by persisting these data points). The UI also needs to look nicer and work on a variety of screen sizes. I'll also want to enable alerts to the user (as the app will normally be in the background) to tell the user when they've spent a particular amount of money (total or in gas costs). 

To break the work up into bits of functionality to add:
  • Selecting vehicles that are not 'average' which will require:
    • UI to select a vehicle make and model
    • Vehicle data (icon image, fuel efficiency, wear-and-tear cost estimates)
    • Code within the model to use the parameters
  • Selecting and saving parameters such as metric vs imperial
    • UI to select parameters
    • Ability to save parameters between runs of the app
    • Code within the model to respond to parameters (e.g. by displaying metric)
    • Ability to pull defaults based on location (for imperial/metric, local fuel prices)
  • Support for 'trips' 
    • Building a UI to start and stop trips
    • Updating the model to enable starting and stopping the GPS
    • Persisting trips to the phone 
    • Viewing past trip data and total data
  • Support for user alerts (as the app will normally be in the background)
    • When the user has forgotten to turn the trip recording off (could be fancy AI but likely if the speed has just been low enough for long enough)
    • Alerts based on cost parameters (e.g. every dollar of fuel cost or after 25 liters -- when you might need to refuel)
  • Changes to the UI to make it look nicer and to adapt to various sized phones
  • Unit tests to help keep the project relatively bug free
Eventually it might make sense to put a server back-end of the app but I don't think that would be in the v1.0 - not that a back-end would not be useful (you could see stats/cost online and not lose data when you upgrade your phone) but I think it will be a useful app without server functionality.