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

voltone
The EEF’s Security WG has released the first public draft of the Secure Coding and Deployment Hardening Guidelines for BEAM languages. “...
New
ob1
Recently we partitioned a table with 28 million rows by its ULID to improve query speeds and reduce query timeouts. To do this I ended up...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
hauleth
Some time ago someone suggested me to write article about how I have configured my Vim to work with Elixir, now there it is: https://med...
New
cheerfulstoic
I spent quite a bit of time trying to find a good configuration to build / test my Elixir app in CircleCI and then push an image to Docke...
New
GenericJam
Just leaving some breadcrumbs for future me and future others like me. Connect with TCP (not secured) - most servers will reject but use...
New
zenw0lf
Hello all! For those wanting to try your hands at Elixir / Phoenix, I wrote a comprehensive tutorial on doing a simple JSON API with sai...
New
jswny
Hello everyone, I recently redesigned my entire deployment process for Phoenix apps based on Docker. I really like the strategy that I ca...
New
fmcgeough
pipe into case? I use that fairly frequently…unless I’m misunderstanding what you’re wanting…could be… its still very early… str = "Hell...
New
caspg
Hi everyone, I recently implemented a real-time search feature in a Phoenix application using LiveView and Tailwind, and I wanted to sha...
New

Other popular topics Top

JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
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
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

We're in Beta

About us Mission Statement