Let’s build a Productivity Timer App with Elm

NewMountain
16 min readJul 30, 2016

--

So my productivity has fallen a bit lately. While I don’t want to get into the details too much, suffice to say I made a proof of concept that was the victim of its own success. It was a super-fun experience and something I am very proud of, but it has also given me a greater appreciation of thinking, designing and architecting first.

However, I still need to get things done and am trying to find a happy medium having spent some time on both ends of the pendulum’s swing. An article from another software developer recommended the Pomodoro Technique. I have no experience with it, but it seemed like a neat enough idea, so let’s make an app out of it and let’s do it in Elm.

I would also like to take the opportunity to document how I approach the problem and why. I am by no means an expert, in fact I’m likely below average, but as Elm is rather new, I think it is important to just start communicating ideas. If my ideas are good, they can be adopted, if they are bad, they can be identified as such and discontinued. Either way, the Elm community is richer for it.

Step 0. Scratchpad…

I am a firm believer that the best way to start an app is to collect your thoughts. I don’t know why, but writing it out on paper first, somehow makes the whole process, for me, more thoughtful, slow, focused and deliberate. So that is how I will start. Also, the anachronism of a programmer writing things down with pen and paper makes me smile a bit as well.

Please pardon the pseudo-code

My thought process for an app always starts with what I want to see. Usually starting there allows you to concentrate first on what’s most important: the user experience. Once that looks alright, I change tactics a little bit from what I did in the past. In an imperative/OOP language, I would usually just start writing code. With functional programming, particularly Elm and Haskell, it makes sense to pause and think about the data needed to power the user experience. My experience with development in Elm is a lot smoother it starts with data types and flows outward declaratively top to bottom.

The reason this is important is thinking about data before jumping in usually allows you to make sure your foundation is solid and have all the pieces you need over which to apply functions. Plan on having a toggle of some sort: you will likely need some Boolean type in your record to represent that toggle. Plan on having statuses, y0u will likely need a custom datatype for that. People obviously think of the counters up front, but if I were making this in JS or Python, I would just have some counters and at some point, just slap some extra stuff into some dictionaries to make anything else I forgot up front work. To be clear, Python and JS would also benefit from thoughtful design as well, but their dynamic nature affords you more room for easy quick hacks.

So with the UX and the data squared away, I sketch out the messages I will need. This likely won’t be all, but having it all out on paper helps see the actions and paths a user can take and if there is anything that can be abstracted, combined or re-used.

I don’t approach my sketches as anything more than waving in some direction. Ultimately, I try to keep my development like a favorite Vonnegut quote of mine: “Bizarre travel plans are dancing lessons from God.”.

Step 1: Enough talking CODE!!!

You know the drill:
1. Make the directory

mkdir timerApp && cd timerApp

2. Set yourself up

git init && vim .gitignore i/elm-stuff:wq

3. Make your Main.elm!

Step 2: Treat your Elm app like a MOVIE

Please forgive the terrible pun, but mnemonics are are lovely trick to make it easier to remember all sorts of details and to make life a touch easier. Whenever I make an Elm app, I start by thinking M.U.V.I. (read: movie), my Model, Update, View and Init.

A. Model : This is where your data goes.

B. Update: This is where you define the actions in your apps, and what happens when they occur.

C. View: This is where you define how that data translates into painting the DOM

D. Init: The data when the application starts (not required, but helpful, both for your Elm application and my lame mnemonic)

Step 3: Our first bit of taste of our app

For Elm development, I have been using Atom, but have been experimenting with VS Code and feel a touch of guilt for leaving my trusty Sublime. Any of them should be fine as long as you install the latest language support, and Elm-Format, a killer-app for any Elm developers. After a few moments, you should have something that looks a bit like this:

module Main exposing (..)import Html exposing (text)main =
text "Ahoy world!"

If your Atom Elm linter is anything like mine, it throws a funny error like this:

Fear not young Padawan

As intimidating as it may look, this error will allow us to talk about why, I think, it’s not working and one of the critical things you need to do in Elm that doesn’t get as much focus as it should: you need to install some dependencies for pretty much any Elm app to work. So let’s do that!
Just about every project will need: elm-lang/core and elm-lang/html and you will need to install them so open up a command line/terminal and type in:

elm-package install elm-lang/core

and you should see something friendly like this:

Make it so!!!

Do that again for `elm-lang/html` and you should be ready to go. Now just type into the terminal:

elm-reactor 

and you should have a little development server you can open up on localhost:8000.

Gloriousness!

Step 4: Quantify everything.

So let’s drop this code and take a look:

module Main exposing (..)import Html exposing (..)
import Html.App as App
-- Modeltype Status
= Relax
| Focus
type Mode
= Elapsed
| Remaining
type alias Model =
{ counting : Bool
, timerStatus : Status
, timerMode : Mode
, seconds : Int
, pomsCompleted : Int
}
-- Updatetype
Msg
-- Just a plug for now so we can compile.
= NoOp
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
( model, Cmd.none )
-- Viewview : Model -> Html Msg
view model =
text "Ahoy world!"
-- Initinit : ( Model, Cmd Msg )
init =
( { counting = False
, timerStatus = Relax
, timerMode = Elapsed
, seconds = 0
, pomsCompleted = 0
}
, Cmd.none
)
-- Subscription
-- Trust me on this one (we can make MUVIS instead!!!)
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- Mainmain : Program Never
main =
App.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}

Neat. Please look through that a few times. Nothing about it should be super crazy, but just make sure it really sinks in (think MUVI(S)!).

Step 5: The time has come.

So let’s get the clock set up as well as some helper functions. For the clock, we will use subscriptions. Something kind of cool is the thought that time is a something to which we subscribe (IRL, I wish I could choose not to…). Let’s do that now.

So we change our subscription by subscribing to time and adding an import up top:

module Main exposing (..)import Html exposing (..)
import Html.App as App
import Time exposing (Time, second)

and our subscription is now:

-- Subscription
-- Trust me on this one (we can make MUVIS instead!!!)
subscriptions : Model -> Sub Msg
subscriptions model =
Time.every second Tick

but it’s our update that really gets an overhaul:

-- Updatetype Msg
= Tick Time
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
-- I don't care about the time, I just want the tick
Tick _ ->
case
( model.counting
, model.timerStatus
, model.seconds
)
of
-- Not counting, so do nothing
( False, _, _ ) ->
( model, Cmd.none )
-- Counting and the clock has struck
-- 25 minutes in Focus
( True, Focus, 1500 ) ->
( model
|> flipStatus
|> zeroClock
, Cmd.none
)
-- Counting and clock has struck 5 minutes in Relax
( True, Relax, 300 ) ->
( model
|> flipStatus
|> zeroClock
|> markPomComplete
, Cmd.none
)
-- Ordinary counting
( True, _, s ) ->
( model
|> tickSecond s
, Cmd.none
)
tickSecond : Int -> Model -> Model
tickSecond s model =
{ model | seconds = s + 1 }
flipStatus : Model -> Model
flipStatus model =
case (model.timerStatus) of
Focus ->
{ model | timerStatus = Relax }
Relax ->
{ model | timerStatus = Focus }
zeroClock : Model -> Model
zeroClock model =
{ model | seconds = 0 }
markPomComplete : Model -> Model
markPomComplete model =
{ model | pomsCompleted = model.pomsCompleted + 1 }

Believe it or not, that’s the lion’s share of the coding. We need to add some UI and some button clicks, but the real business of this application, the ticking, the mode changes, and the counting of complete cycles is done.

However, that really doesn’t feel gratifying. We want some bouncing widget on the screen to justify our efforts. So let’s do that:

Update our imports:

module Main exposing (..)import Html exposing (..)
import Html.App as App
import Html.Events exposing (..)
import Time exposing (Time, second)

Add some more updates to our already meaty update function:

-- Updatetype Msg
= Tick Time
| Start
| Pause
| Clear
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
-- I don't care about the time, I just want the tick
Start ->
( model
|> startCounting
, Cmd.none
)
Pause ->
( model
|> stopCounting
, Cmd.none
)
Clear ->
( model
|> stopCounting
|> zeroClock
|> resetPomsCompleted
, Cmd.none
)
Tick _ ->
case
( model.counting
, model.timerStatus
, model.seconds
)
of
-- Not counting, so do nothing
( False, _, _ ) ->
( model, Cmd.none )
-- Counting and the clock has struck
-- 25 minutes in Focus
( True, Focus, 1500 ) ->
( model
|> flipStatus
|> zeroClock
, Cmd.none
)
-- Counting and clock has struck 5 minutes in Relax
( True, Relax, 300 ) ->
( model
|> flipStatus
|> zeroClock
|> markPomsCompleted
, Cmd.none
)
-- Ordinary counting
( True, _, s ) ->
( model
|> tickSecond s
, Cmd.none
)
resetPomsCompleted : Model -> Model
resetPomsCompleted model =
{ model | pomsCompleted = 0 }
stopCounting : Model -> Model
stopCounting model =
{ model | counting = False }
startCounting : Model -> Model
startCounting model =
{ model | counting = True }
tickSecond : Int -> Model -> Model
tickSecond s model =
{ model | seconds = s + 1 }
flipStatus : Model -> Model
flipStatus model =
case (model.timerStatus) of
Focus ->
{ model | timerStatus = Relax }
Relax ->
{ model | timerStatus = Focus }
zeroClock : Model -> Model
zeroClock model =
{ model | seconds = 0 }
markPomsCompleted : Model -> Model
markPomsCompleted model =
{ model | pomsCompleted = model.pomsCompleted + 1 }

and finally show our new view function. Pro-tip, when I am developing apps, I like to see my model on the screen, so I usually keep the whole model printed on the DOM until I am putting final touches on the UI.

-- Viewview : Model -> Html Msg
view model =
div []
[ text "Ahoy world!"
, p [] [ text <| toString model.seconds ]
, br [] []
, button [ onClick Start ] [ text "Start" ]
, button [ onClick Pause ] [ text "Pause" ]
, button [ onClick Clear ] [ text "Clear" ]
, br [] []
, p [] [ text <| toString model ]
]

Also, I don’t know about you, but for basically every blog post with code, I find a way to mess it up, so I will just post the entire code up to now below. Feel free to skip it, but if something isn’t working for you and you are yelling at the screen in frustration, please forgive me. Copy and paste below and fear not: we’re going to get through this! In fact, you’re doing great!!!

module Main exposing (..)import Html exposing (..)
import Html.App as App
import Html.Events exposing (..)
import Time exposing (Time, second)
-- Modeltype Status
= Relax
| Focus
type Mode
= Elapsed
| Remaining
type alias Model =
{ counting : Bool
, timerStatus : Status
, timerMode : Mode
, seconds : Int
, pomsCompleted : Int
}
-- Updatetype Msg
= Tick Time
| Start
| Pause
| Clear
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
-- I don't care about the time, I just want the tick
Start ->
( model
|> startCounting
, Cmd.none
)
Pause ->
( model
|> stopCounting
, Cmd.none
)
Clear ->
( model
|> stopCounting
|> zeroClock
|> resetPomsCompleted
, Cmd.none
)
Tick _ ->
case
( model.counting
, model.timerStatus
, model.seconds
)
of
-- Not counting, so do nothing
( False, _, _ ) ->
( model, Cmd.none )
-- Counting and the clock has struck
-- 25 minutes in Focus
( True, Focus, 1500 ) ->
( model
|> flipStatus
|> zeroClock
, Cmd.none
)
-- Counting and clock has struck 5 minutes in Relax
( True, Relax, 300 ) ->
( model
|> flipStatus
|> zeroClock
|> markPomsCompleted
, Cmd.none
)
-- Ordinary counting
( True, _, s ) ->
( model
|> tickSecond s
, Cmd.none
)
resetPomsCompleted : Model -> Model
resetPomsCompleted model =
{ model | pomsCompleted = 0 }
stopCounting : Model -> Model
stopCounting model =
{ model | counting = False }
startCounting : Model -> Model
startCounting model =
{ model | counting = True }
tickSecond : Int -> Model -> Model
tickSecond s model =
{ model | seconds = s + 1 }
flipStatus : Model -> Model
flipStatus model =
case (model.timerStatus) of
Focus ->
{ model | timerStatus = Relax }
Relax ->
{ model | timerStatus = Focus }
zeroClock : Model -> Model
zeroClock model =
{ model | seconds = 0 }
markPomsCompleted : Model -> Model
markPomsCompleted model =
{ model | pomsCompleted = model.pomsCompleted + 1 }
-- Viewview : Model -> Html Msg
view model =
div []
[ text "Ahoy world!"
, p [] [ text <| toString model.seconds ]
, br [] []
, button [ onClick Start ] [ text "Start" ]
, button [ onClick Pause ] [ text "Pause" ]
, button [ onClick Clear ] [ text "Clear" ]
, br [] []
, p [] [ text <| toString model ]
]
-- Initinit : ( Model, Cmd Msg )
init =
( { counting = False
, timerStatus = Relax
, timerMode = Elapsed
, seconds = 0
, pomsCompleted = 0
}
, Cmd.none
)
-- Subscription
-- Trust me on this one (we can make MUVIS instead!!!)
subscriptions : Model -> Sub Msg
subscriptions model =
Time.every second Tick
-- Mainmain : Program Never
main =
App.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}

Step 6. A picture is worth a thousand words.

So let’s make a view function and make it pretty. As discussed in a prior blog post, declarative DOM functions are one of the major selling points of React. Thankfully, it’s equally, if not more, awesome in Elm. Just keep in mind what I said earlier, Elm seems to flow best from top to bottom. So let’s start with the high level and work our way down to the nitty-gritty.

view : Model -> Html Msg
view model =
div []
[ makeHeader
, makeMainPage model
, makeFooter
--- Purely for sanity checking will be removed later
, br [] []
, p [] [ text <| toString model ]
]

Our app is divided up into a header, which takes no model as it just renders static content, a mainPage which gets a model for dynamic content, and a footer without model for the same reasons as header. Let’s knock out the header and footer.

makeHeader : Html Msg
makeHeader =
header []
[ div [ class "title" ]
[ h2 [] [ text "Work Relax Timer" ]
]
]
makeFooter : Html Msg
makeFooter =
footer []
[ div [ class "links" ] [ linkMaker ]
, div [ class "logo" ]
[ img [ src "/static/Signature.JPG" ] []
]
]
linkMaker : Html Msg
linkMaker =
ul [] <| List.map linkRenderer getLinks
getLinks : List ( String, String )
getLinks =
[ ( "Medium", "https://medium.com/@NewMountain" )
, ( "Twitter", "https://twitter.com/@nyberg_c" )
, ( "GitHub", "https://github.com/NewMountain" )
]
linkRenderer : ( String, String ) -> Html Msg
linkRenderer ( name, url' ) =
li []
[ a [ href url' ] [ text name ]
]

Cool. It may not look like much yet, but give it a few moments and a sprinkling of CSS. For now, let’s finish up with the main portion of our app with the mainPage function.

makeMainPage : Model -> Html Msg
makeMainPage model =
div []
[ makeClock model
, makeButtonCluster
]
makeButtonCluster : Html Msg
makeButtonCluster =
div [ class "btncluster" ]
[ button [ onClick Start ] [ text "Start" ]
, button [ onClick Pause ] [ text "Pause" ]
, button [ onClick Clear ] [ text "Clear" ]
]
makeClock : Model -> Html Msg
makeClock model =
div [ class "bezel" ]
[ div [ class "clock" ]
[ div [ statusChecker model.timerStatus ]
[ text <| toString model.timerStatus
]
, div [ class "gauge" ]
[ text <| timeMaker model
]
]
, bezelButtonMaker "Elapsed" ElapsedMode model
, bezelButtonMaker "Remaining" RemainingMode model
]
timeMaker : Model -> String
timeMaker model =
case ( model.timerMode, model.timerStatus, model.seconds ) of
( Elapsed, _, s ) ->
getClockString s
( Remaining, Relax, s ) ->
getClockString <| (relaxLimit - s)
( Remaining, Focus, s ) ->
getClockString <| (focusLimit - s)
getClockString : Int -> String
getClockString sec =
let
formatter x =
if (String.length <| toString x) == 1 then
"0" ++ toString x
else
toString x
madeMinutes =
sec // 60
madeSeconds =
rem sec 60
in
formatter madeMinutes ++ " : " ++ formatter madeSeconds
statusChecker : Status -> Html.Attribute Msg
statusChecker status =
case status of
Relax ->
class "relaxgauge"
Focus ->
class "focusgauge"
bezelButtonMaker : String -> Msg -> Model -> Html Msg
bezelButtonMaker btnName msg model =
button
[ onClick msg, getBezelBtnClass btnName model ]
[ text btnName ]
getBezelBtnClass : String -> Model -> Html.Attribute Msg
getBezelBtnClass btnName model =
if btnName == (toString model.timerMode) then
class "activebezelbtn"
else
class "inactivebezelbtn"

Cool. It’s all there now. You can see how we started from the top and built down to simple DOM elements and implemented some custom rendering functions in between. You can chop these functions up as much as you like, but this looks alright to me for now. After we clean up the front end with some CSS, we can look at refactoring this code a bit, but for now, this is solid.

Step 7. COFFEE BREAK!!!

Turn on your app. Watch it work. Smile. You’re a boss. Go grab a cappuccino, you earned it.

Step 8. Make it pretty.

Cool. So now we need to put it into a proper web-project of some kind. For now, I will shamelessly recycle a gulp file, an Index.html, and an app.css I have from another project. So let’s do that. Our file tree should now look like this:

tree -I 'elm-stuff|node_modules'.├── Index.html├── Main.elm├── Signature.JPG├── app.css├── bundle1.js├── elm-package.json├── gulpfile.js└─ package.json

I’m going to do something truly bold here as someone who spends most of his time writing back-end code. I’m not going to use Bootstrap. Instead I will write (and copy) some CSS to make this app a bit more special. Let’s see how that goes.

Step 9. Now I remember why we all so gladly went to Bootstrap…

Well after an embarrassing amount of time spent on CSS, I got our app to MVP status and am declaring it ready enough. Next time, I will definitely use Bootstrap or Material-lite instead of raw CSS.

Excepting dramatically overestimating my CSS skills, that went pretty well. One other thing I noticed at the last minute, was the real life application gives breaks after four sessions. I added a boolean, chilloutMode, to the record, refactored the logic in the update and the flipStatus function and all was done. Better still, the Elm compiler pointed out the half dozen places out of alignment with the new types, I corrected them and everything just worked. Very nice.

Want to know something cool? I wrote a half decent app from scratch which dealt with text parsing and a bunch of custom logic. When I was done, I made a major breaking change to the API. You know how many times I wrote a console.log to figure out what was happening? NOT ONCE.

Not bad for a one night project.

I hope this was helpful and I invite you to look through the code here. Next post I plan on writing an application with several functionalities so I can show how to approach single page applications, defining what a “component” is in Elm and how to pass messages and state between those “components”. If you have any applications you would like to see, please let me know.

If you like what I’m doing and what you read, please share and subscribe to this blog on Medium as I plan on doing a lot more of this. Also, please let me know what you are confused about and would like to see next. Let’s keep growing the list of cool things we do with Elm together!

--

--