tfwright

tfwright

LiveView learning application

I thought I’d share a small project I’m working on to gain some familiarty with LiveView in a Phoenix app.

Github Repo

Deployment

It’s a little timer designed around interval exercises. Basically it allows you to build a list of sets of timed exercise intervals, which can be repeated, with periods of rest in between each interval.

You can browse the commit history to see how the project developed. I started with a bare bones Ecto-less Phoenix app, and I just added a simple LiveView module to handle form events for the exercises. I also added a timer with controls that would loop through the data from the form and update the assigns sent to the view, allowing the user to see a count down.

As a next step I added the concept of ‘sets’ so that intervals could be grouped and repeated, with rest in between.

This was all (template, helpers, timer etc) in a single module, so finally I refactored a bit by pull out the different parts into separate files, namely, a template and helper file. If I continue, I will probably extract some of the data structures from the LiveView logic into schemas.

Overall, I’m quite impressed with LiveView. It was really nice not to have to wire a bunch of boilerplate JS API integration in order to see a working UX. I ran into a lot fewer typical issues with JS syntax errors, browser refreshing and console logging, etc. That whole painful process was just skipped. Conversely, I immediately ran into the typical challenges in managing state for a dynamic UX, immutable data updates, etc. I think that’s a good thing, because it brought the real problems in my domain into clearer focus, and let me bring the power of Elixir to bear in solving them, which for me was quite an advantage of any flavor of JS with any number of libraries assisting.

I should note that this application is probably not the ideal candidate for LiveView, since it requires a very long-running BEAM process just to countdown a timer, something that would probably be better handling on the client side so it takes up fewer server resources. But it was something I actually wanted to use so it gave me some motivation.

Hope this is of some help to anyone else interested in getting started with LiveView. Let me know your thoughts.

Most Liked

tfwright

tfwright

As promised, in the latest version I’ve converted some of the data structures into Ecto schemas. For extra learning, I put off actually setting up a Repo, and used embedded_schema for everything. It was pretty cool how easy it that was to accomplish.

On the LiveView side, this separation naturally created some separation in the UI, between the phase when a timer is being created and when it is being viewed/used. I took advantage of this by separating the form UI into a separate component. I began by trying to keep the change as incremental as possible by using LiveComponent to ‘embed’ the form into the timer view like it worked before, but that proved really awkward with the timer event handling. Again, this probably mostly shows that the timer logic shouldn’t be in Elixir at all and in a real application I probably would have converted it to JS at this time (if I hadn’t started there, which I almost certainly would have). But another easy solution was just to add a new top level live view for the timer, which I wanted anyway so that there would be a unique URL users could visit to reuse the same timer. Since this still isn’t backed by a DB, I added a simple Agent to store uuids.

Next step will probably be added some style/UI sugar so it’s a bit more usable, then maybe think about more permanent storage options. Rather than a relational DB, one option I’m considering is just adding a JSON export/import.

tfwright

tfwright

Latest update: JS beep sound alerts via Hooks.

Another “MVP” feature for this toy app is the ability to play sounds to alert the user when an interval is over. Can’t really expect someone that’s in the middle of a strenuous exercise to keep their eyes trained on a screen. So after adding bulma and smoothing out the design a bit (CSS ugh) I started looking into how to implement some sort of audio component.

First armed with what I thought was a prefab npm package, I utterly failed to get things working. Although I was able to require the package in app.js just fine, the function wasn’t behaving as expected in my .leex template. At first I thought I was having problems with my webpacker config (one of the many reasons I want to learn LV in the first place), but actually I think the issue is with how LV injects its own scripts into the page.

After reading a bit, specifically @mindok’s very helpful posts here, it sounded like the easiest way to manage template scripts is to use LV’s hook API. And it turned out to be very easy. I just c/ped some SO code (as one does) and dropped it into the mounted hook for a span tag I would render when I wanted a beep.

Definitely unsure if it’s best practice to conditionally render tags with hooks assigned in this way. It’d definitely feels like a bit of an awkward work around, but seems to work pretty well. It’s also worth noting again that it is again the original bad design of implementing the timing code server-side that made this a problem in the first place. Good example of how not to architect a production app, but in this case that’s a good thing because the point is to learn and now I know about hooks (and it never hurts to get a reminder about the benefits of using the right tools either).

Link to deployed app is in the OP. If you have any experience using LV hooks and have an opinion about their proper use, lmk.

outlog

outlog

good fun with the beep - maybe just note ios and desktop safari is auto-muted:/

tfwright

tfwright

Good tip! I’ll look into that.

slouchpie

slouchpie

HI there.

I recently added “beep” to a live view page and I also used the mounted callback.
However, I did not do it by calling beep() directly in the mounted callback. Instead, I used handleEvent like this:

In app.js:

Hooks.MyElement = {
  mounted() {
    this.handleEvent("play_beep", (payload) => {
      beep();
    });
  },
};

Then whenever my LiveView component handles an event with this kind of return:

In my_live.index.ex:

  def handle_info({SomeModule, [:something | [:happened]], _}, socket) do
    {:noreply, push_event(fetch(socket), "play_beep", %{})}
  end

So you could have the interval done in elixir instead. There are probably other ways to approach this, and my use case is not the same as yours (I don’t need an interval). Still, hopefully it helps.

Docs here: https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks

P.S. the client can push events too. Look at pushEvent in the docs.

P.P.S. You might have to upgrade your phoenix_live_view version.

Where Next?

Popular in Guides/Tuts Top

hauks96
Hello everyone, I created a deployment tutorial for Phoenix applications with Kubernetes (microk8s) a few months back with the goal of s...
New
siever
I just wrote a simple guide on how you can setup a productive elixir development environment in vim. Its really easy, just a few steps. ...
New
kokolegorille
Hello dear alchemists, There was this question some days ago here about the deployment to a VPS. As I was in the process of deploying t...
New
nelsonic
Complete beginners Todo List Tutorial in Phoenix 1.5.3 (latest and greatest): It’s a feature complete TodoMVC clone with 0% JavaScri...
New
tomciopp
TLS 1.3 has been out for a little over a year now, but it has been unavailable in Phoenix due to erlang’s handling of ssl. With the most ...
New
Eiji
Hey, today I give amnesia library a try and found a few problems. I would like describe how to setup it properly and solve problems which...
New
jtormey
Hello! Having written a lot of LiveView code, I’ve made some VS Code snippets to speed up writing callbacks for LiveViews and LiveCompon...
New
TwistingTwists
This is a thread to note down things/best practices encountered in LiveBeats App as I explore the source code. Found this usage of r...
New
water
I’ll post this here, It might help someone in the future. Feedback is greatly appreciated. I use it with direnv on NixOS, It should wor...
New
fuelen
Hi all! Just want to share a small code snippet which allows writing CASE expressions using macro which is similar to cond. Here is an ...
New

Other popular topics Top

stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement