Pelmodoro - a Pomodoro app built with Elm

There are many Pomodoro apps out there of different sizes and colors. After using some of them I tried to build my own. Pelmodoro is the result of that effort.

The app has the traditional timer and a few other features:

  • Personalize the number of rounds and sessions duration (work, break, and long break);
  • Control how the timer will behave after each session ends;
  • Control how you will be notified about session endings;
  • Themes!!! 🎨
  • Spotify integration to play any playlist during your work rounds;
  • Rate your work sessions;
  • App usage stats, including worked minutes, breaks, and more;
  • Import/export your stats data.

On top of that, Pelmodoro is an off-line first PWA, so you can install it on your mobile device or desktop. I have been using it as a stand-alone app with Edge's PWA support.

The project is open-source and you can check it out @ GitHub.

Yet another Pomodoro app? Why?

There isn't one reason why, and the answer could be just "because I wanted" 😁 But I also wanted to create something with Elm, a language I've been using daily for the last year and a half, but that I have never used to build something from my own.

Besides, I was using Habitica (an RPG game like that helps you achieve your goals and daily tasks) to track my productivity but I felt it was overkill. The idea was to replace Habitica building some of the features that made sense for me into the Pomodoro app, mainly the round rating system.

Making it work

The main tool I used to build the app was the Elm language and its ecosystem. It is not a large ecosystem, but it offers lots of quality packages that really surprised me during development.

Elm is a functional language, strongly typed and pure (with controlled side effects) targeted at front-end development. Although it is not popular, I was impressed to see that most of the problems I needed to solve were already solved by the community.

  • For the timer rendering I used SVG and the excellent official package to create and handle SVG documents;
  • I used elm-css for the CSS, which allowed me to write safe and dynamic styles;
  • For dates handling I used the date package;
  • The calendar on the stats area was easily solved using the calendar package.

For some of the features, I had to use JavaScript, mainly the Spotify integration. Luckily Spotify has a reasonlably complete documentation for its API. After fighting the PKCE auth system, developing the integration was easy.

To persist the app's state I used both localStorage (for settings and the timer state) and IndexedDB (to persist usage stats). Instead of using the IndexedDB API directly, I used Dexie which abstracts most of the IndexedDB complexities.

To play sounds I used the howler.js lib.

Making it beautiful

After being satisfied with the features and the way everything looked and worked, I showed my code to some people that gave me valuable feedback, mainly about my code structure. In about two days I refactored my app's structure completely. When I was done I had a PR adding 5,934 lines and removing 3,756.

If that was a JavaScript project, that would be a very daunting PR, but I'm using Elm so refactors are safe and cheap. If it compiles it probably works as intended, so I could just merge the PR without blinking twice.

The original code structure grew very organically during development, which produced a functional code but was ill-organized. One example was that I was separating Model, Msg, and Types on different modules. The idea was to avoid import cycles, but that was just evidence that my code needed better structure.

Looking at the Real World application I could see that there were better ways to structure my modules using nested TEAs and keeping the Main module as a hub for everything in the app.

My original update function was massive, but after separating messages for each "page" I ended up with a more organized, contained, and easy to understand code.

There is a lot to talk about code patterns with Elm so I would recommend reading Elm patterns for a more in-depth study.

I also implemented a few stylistic decisions to standardize my code such as:

  • Avoid exposing constructors in module definitions;
  • Avoid exposing functions and types when importing modules;
  • When aliasing a module, use the module's own name, mimicking Elixir's alias. Ex.: Html.Attributes as Attributes;
  • In case of name conflicts a) don't alias at all or b) join module's names. Ex.: Svg.Attributes as SvgAttributes;
  • Prefix every view function with view 👀

The idea behind most of these was to make the code more explicit, making it clear where types and functions are coming from as well as their effects.

Final thoughts

Although I did this big refactor I know my code structure and design could improve at various places, but I want to finish the project. I feel like software in general is never really done, except for a few outliers, so I need to stop investing so much time on this project that is already working very well for me. That way I can invest my time on another side project that will suck out all my free time 🤡

In general, I'm very satisfied with the final result and have been using the app daily. With some luck other people will find it useful as well and if you are one of those people I'll be extremely happy to know 😊

25