Himmel

Utility Software

altText

Summary

Himmel provides current and forecasted weather from any web browser. You can check conditions for your current location, save new places, and get hourly updates while the app is open. This was the final project for a training program I participated in for software development. After delivering an MVP, I continued developing it by adding a cache, an hourly scheduler, and error handling, then deployed it live. A full project outline is available below.

Media

Features

  • Search for, save, and remove places
  • Use as a weather dashboard with automatic, hourly updates (registered users only)

Design / Tech

Code:

  • Elixir
  • HTML
  • CSS

App:

  • Phoenix LiveView

DB:

  • Postgres

Libs:

  • Tailwind

Design:

  • Figma

Consideration

When reviewing final project ideas for Dockyard Academy, an intensive 3-month training program for software development, I was looking for something challenging where I could apply my skills in content and UI design.

My advisor and I discussed the merits and feasibility of several potential concepts on a shared whiteboard. I tossed out impractical and content-heavy ideas and chose a weather app.

Problem Identification

Using the same weather app or service across mobile and desktop devices isn't easy unless you're using Apple. For people who like to save and monitor multiple locations, this can be difficult.

A web app though never needs to be downloaded from an app store and it'll work on any device with a web browser. Secondly, it can have a flexible design to adapt to any screen size. For those so inclined, it can be used on a giant wall monitor or for a mini dashboard display.

Market Research

To build a weather app within a short amount of time, I did a quick assessment of various apps in terms of features and UI design. Both Apple and Google had similar and straightforward designs, while commercial apps from AccuWeather and others were information-dense and often mixed with advertisements. I chose to copy the Apple Weather app since I have an iPhone.

Planning

Scope

The project requirements entailed building a web application using a database with entity relationships, 3rd party network APIs, PubSub for event broadcasting, user authentication, and a dynamic UI. Upon completion, it would be solely assessed on its technical aspects and meeting these requirements.

The development timeline was 6 weeks, followed by a group presentation. I decided the MVP would provide:

  • A location's current and forecasted temperature and weather conditions for 36 hours and an average for the following 10 days, using Celcius and weather symbols.
  • The ability to search for, save, and delete places using a registered account, with the option to try out the app without registration.

For the 2nd version, I planned to refactor some code, polish the UI, implement PubSub for hourly weather updates, and make additional improvements as encountered.

Technology Selection

With the training program being Elixir-oriented, I used that language with Phoenix as the web framework, and LiveView for the frontend. For the database, I chose PostgreSQL because it's commonly used with Phoenix. And for the UI design, I went with TailwindCSS as I'm very familiar with it.

After finishing the project's 2nd version, I deployed it with Fly.io because I had recent experience with it from the course.

Design

Himmel would closely mimic the Apple Weather app's screens for the current selected and list of saved places. New designs would be made for user registration, login, and authentication settings.

Prototype to Code

I first designed the prototype in Figma with an iPhone 13 canvas size to test with my matching smartphone. The goal was to define the element groups, layouts, sizes, spacing, colors, and interactions I'd use as a baseline.

When translating these designs to code, I first created CSS variables for colors, then used Tailwind utility classes to progressively implement the intended design with the HTML. Complex and reusable styles were either grouped into components or defined in an application CSS file.

Responsive Layout

On mobile, the "Main" screen only shows the current location's weather with navigation buttons to "Places" and "Settings". Instead of creating 3 separate web pages, Himmel would be a single-page application that would progressively show these screens on larger displays.

I used a 3-column Flexbox where the "Places" screen would appear on tablets, and "Settings" on larger screens. The navigation would adapt accordingly and disappear. As appropriate, spacing was adjusted on the app container in addition to column minimum and maximum widths at breakpoints.

Frontend Development

I began by adding center-aligned wrapper containers in the default page templates provided by Phoenix. AppLive is the master module that loads weather data, handles all events, delegates computation to a Utility module, and provides the UI skeleton. The interface was built by first using styled elements and blocks, then refactoring them into components as complexity grew.

Component Architecture

The AppLive module renders a base template with mobile/tablet navigation, a desktop header, an advisory banner, and 3 panels. The "Main", "Places", and "Settings" panels were first LiveComponents with their own state management and event handling. However, after realizing they couldn't directly send each other event messages (since they share the same LiveView process), I converted them to normal components. AppLive would pass variables down to all its UI children, which then reacted to the overall app state and their own needs.

Logic

AppLive has a mount function to provide an authenticated user and manage data loading, and event handlers for interaction. With time, I moved core logic to a controller Utils module where we'd request the current location's weather, prepare data or errors for display, and manage places. If there was an authenticated user, we'd take the extra steps to sync with the database, subscribe them to hourly updates, and process them once they're all received.

Backend Development

Geolocation and Search

When first starting Himmel, the "Main" screen would need to identify the user's current location to retrieve its weather. Although requesting user access to the browser's Geolocation API would be most accurate, it was much faster and non-intrusive to infer their location via IP address.

Only once in an app staging environment could I get my IP module to grab the first IP in an HTTP x-headers list provided by my host. This was then passed to ipinfo.io with an API key and an Erlang library I found, which returned a name and geocoordinates.

When clients searched for places, a geocoding module would receive a query, pass it to an API at open-meteo, and return a list of data structures to the frontend with just enough details for display and a weather query.

Weather Data

After browsing various data providers, I found Open-Meteo easiest to use for small projects. In a weather module, an API query with geocoordinates and settings returned a raw data object. This was processed into a data structure using a function pipeline for a place's current, hourly, and daily forecasts. The frontend requested this structure for display.

For the project's 2nd version, I wanted to standardize the weather data as I wanted to add a cache and an hourly updates scheduler. So I made a typed WeatherInfo struct using the Domo library, which helped catch errors during development.

To minimize API calls if such an app were to get popular and need to scale, I had all requests pass through a cache using the Cachex library with a TTL of 30 minutes.

Authentication

User accounts were set up using the Phoenix auth generator for LiveView. This updated the database and added routes, permissions logic, and web pages for users to log in and manage their accounts. After updating the router to include a user session for the app, I added session checks to the frontend logic to enable database interaction and hourly weather updates.

Hourly Updates

The goal was to send hourly weather updates to all active clients. This entailed the following processes:

  1. Clients would subscribe to updates for each of their places.
  2. A Presence process tracks each place's subscribers.
  3. Using the Quantum library, a CRON job would be set up using a scheduler module.
  4. Every hour, a job would get all the places stored in the cache.
  5. For each place, check with Presence if it has any subscribers.
  6. If so, try and get the weather update and broadcast the result.
  7. Places with no remaining subscribers were dropped from the cache.
  8. Process all place updates concurrently with a max of 10 requests.
  9. Clients collect place updates until all are received, then update the DOM in one go.

Database

At first, I modeled the database entities where a User has many Places, and Places have many Users. So I used a join table to create the relationships but ran into trouble when considering how many-to-many records should be managed. Another Elixir developer provided valuable feedback: Only build database entities if they'll be used or queried as distinct resources. Otherwise, you'll build unnecessary complexity and have more test suites with initial state to set up and run.

I ended up with Users (resource) having many Places (embedded schema), each having one Coordinate (embedded schema). Aside from fields one would expect, I added:

  • User "active_place_id" is the most recent Place viewed in the frontend, so it could be loaded into the "Main" screen when the app starts.
  • Place "location_id" is a unique reference based on its Coordinates, which is needed across the app logic since the database doesn't provide one.
  • Place "weather" is a virtual field to hold a WeatherInfo struct, which shouldn't be saved in the database.

Aspects for Improvement

Geolocation

IP-based geolocation from my experience in Germany has been wildly inaccurate based on my ISP which will return one of 3 large cities instead of my own. To quickly help users, an info banner automatically opens with the app that informs people where to change their location.

To improve upon this, one could ask the user's permission for their browser's geolocation API. To do this tactfully, I would modify the intro banner to offer the opportunity to improve accuracy through permissions, which when clicked, would trigger the sequence.

Error Handling

As Himmel progressed, I had to address potential network data request errors. The important thing was providing some feedback to the client on what kind of error happened. I used structured errors so the frontend could handle them with a banner and a message corresponding to the error.

While this was handled for weather data requests, failed user IP requests default to a few random European cities, and failed geocoding requests will silently fail. These would ideally be handled similarly to weather data.

Testing

During initial development, I was gradually figuring out the right data structures to use and processing them for frontend use. Now that the app is stable, it makes more sense to implement testing.

Deployment

Launch

Code was continuously version-controlled and available on GitHub. Once ready, I signed up for a new account with Fly.io, installed their CLI, and followed instructions for setting up a database and including secrets. While the process was straightforward, it did take multiple attempts to fix little quirks.