Understanding LiveView: build a Gallery app
Tags: elixir, phoenix, liveview
Published: 2019-11-28 19:31:26

---
title: Understanding LiveView: build a Gallery app
published: true
date: 2019-11-20 17:50:13 UTC
tags: elixir,phoenix,liveview
canonical_url: https://www.poeticoding.com/understanding-liveview-build-a-gallery-app/
cover_image: https://1o6jjh24p1t92lcqci49qg8a-wpengine.netdna-ssl.com/wp-content/uploads/2019/11/aws_s3_in_elixir_with_exaws.jpeg
---
**Understanding Phoenix LiveView**

* [Setup](https://www.poeticoding.com/how-to-setup-phoenix-liveview/)
* [The Primitives](https://www.poeticoding.com/the-primitives-of-phoenix-liveview/)
* Building a Gallery app - (this article)


{% youtube P5TvNf65LLw%}

**[Subscribe to get FREE Tutorials by email](https://pages.convertkit.com/c021826e86/5ddcb20a27)**. Start learning Elixir and Phoenix to build features and apps!


In the previous articles we've seen [how to setup Phoenix LiveView](https://www.poeticoding.com/how-to-setup-phoenix-liveview/) and [built a counter](https://www.poeticoding.com/the-primitives-of-phoenix-liveview/) (which is like the "Hello World" code example in the LiveView world). We’ve also taken a look at the [live-cycle](https://www.poeticoding.com/the-primitives-of-phoenix-liveview/), inspecting websocket messages, having a glimpse of how the things work under the hood.

It's now time to build our Gallery app! For simplicity we are not going to use any database. Our images will be just a fixed set of urls taken from [Unsplash](https://unsplash.com/).

![Gallery app with Slideshow](https://www.poeticoding.com/wp-content/uploads/2019/11/0_final_result.gif)


We are about to see different things, starting with a simple draft of the app and then going through a refactoring of the code, and building features like thumbnails and a slideshow option.


## From a counter to a gallery

We start by using the counter's code we’ve seen in [the previous article](https://www.poeticoding.com/the-primitives-of-phoenix-liveview/).

At the moment we aim to just get a working draft of a gallery - we’ll spend time later to refactor the code and add more functionalities.

```elixir
# lib/gallery_web/live/gallery_live.ex

defmodule GalleryWeb.GalleryLive do
  use Phoenix.LiveView

  def mount(_session, socket) do
    {:ok, assign(socket, :counter, 0)}
  end

  def render(assigns) do
    ~L"""
    <label>Counter: <%= @counter %></label>
    <button phx-click="incr">+</button>
    """
  end

  def handle_event("incr", _, socket) do
    {:noreply, update(socket, :counter, &(&1 + 1))}
  end
end
```

So we start by copying the counter code in the `GalleryLive` module, checking that the correct route is in place in `lib/gallery_web/router.ex`

```elixir
# lib/gallery_web/router.ex

defmodule GalleryWeb.Router do
  use GalleryWeb, :router
	...

  scope "/", GalleryWeb do
		...
    live "/gallery", GalleryLive
  end

end
```

We have a fixed list of image urls taken from [unsplash.com](http://unsplash.com)

```elixir
[
  "https://images.unsplash.com/photo-1562971179-4ad6903a7ed6?h=500&fit=crop",
  "https://images.unsplash.com/photo-1552673597-e3cd6747a996?h=500&fit=crop",
  "https://images.unsplash.com/photo-1561133036-61a7ed56b424?h=500&fit=crop",
  "https://images.unsplash.com/photo-1530717449302-271006cdc1bf?h=500&fit=crop"
]
```

we need an index to go through these images, incrementing and decrementing it like we did for the counter

![Image List and Index](https://www.poeticoding.com/wp-content/uploads/2019/11/1_index_list-1.gif)


At the beginning the index is `0` and it points to the first image. By incrementing it, the index will then point to the second, third and fourth image. Once reached the end of the list, it will go back to the first one. 

Let's rename  `:counter` to `:idx` in `mount/2`. Then, we change the template in `render/1`, adding two new buttons: *prev*  and *next.* They send two different `"prev"` and `"next"` events, which we handle separately. 

```elixir
defmodule GalleryWeb.GalleryLive do
	use Phoenix.LiveView

	def mount(_session, socket) do
	  {:ok, assign(socket, :idx, 0)}
	end
	
	def render(assigns) do
	  ~L"""
	  <label>Image Index: <%= @idx %></label>
	  <button phx-click="prev">Prev</button>
	  <button phx-click="next">Next</button>
	  """
	end
	
	def handle_event("prev", _event, socket) do
	  {:noreply, update(socket, :idx, &(&1 - 1)}
	end
	
	def handle_event("next", _event, socket) do
	  {:noreply, update(socket, :idx, &(&1 + 1)}
	end
end
```

- `handle_event("next", _event, socket)` is identical to the `"incr"` counter's version, and it increments the `:idx` by `1`.
- `handle_event("prev", _event, socket)` instead decrements `:idx` by `1`.

![Prev decrements and Next increments](https://www.poeticoding.com/wp-content/uploads/2019/11/2_index_prev_next.gif)


It's better to move these updates in two separate functions: `assign_prev_idx/1` and `assign_next_idx/1`. 

```elixir
def handle_event("prev", _event, socket) do
	{:noreply, assign_prev_idx(socket)}
end

def handle_event("next", _event, socket) do
	{:noreply, assign_next_idx(socket)}
end

def assign_prev_idx(socket) do
	socket
	|> update(:idx, &(&1 - 1))
end

def assign_next_idx(socket) do
	socket
	|> update(:idx, &(&1 + 1))
end
```

They take a `socket` and `update/3` the `:idx` returning a new `socket`. There are different reasons why I prefer to move the *update* part into a different function, outside `handle_event`. With just an `update/3` the advantage of doing so maybe is not that obvious, but in this way we keep the `handle_event/3` cleaner, the update's logic easier to test, and if we give a good name to the new function it's also clearer what our `handle_event/3` callback does.

To show the images, we define a new function called `image(idx)`, where the argument is the index, and it returns an image url.

```elixir
def image(idx) do
	[
	"https://images.unsplash.com/photo-1562971179-4ad6903a7ed6?h=500&fit=crop",
	"https://images.unsplash.com/photo-1552673597-e3cd6747a996?h=500&fit=crop",
	"https://images.unsplash.com/photo-1561133036-61a7ed56b424?h=500&fit=crop",
	"https://images.unsplash.com/photo-1530717449302-271006cdc1bf?h=500&fit=crop"
	]
	|> Enum.at(idx)
end
```

For the sake of simplicity, `image/1` uses a small fixed list of image urls. 
We use [Enum.at/2](https://hexdocs.pm/elixir/Enum.html#at/3) to get the image url at the index `idx`. `Enum.at/2` works also with a negative index, going through the list in reverse order

```elixir
iex> ["a", "b", "c"] |> Enum.at(-1)
"c"

iex> ["a", "b", "c"] |> Enum.at(10)
nil
```




When the index is *out-of-bound*, [Enum.at/2](https://hexdocs.pm/elixir/Enum.html#at/3) by default returns `nil`. When the index reaches the end of the list, it should then go back at the beginning pointing to the first element.

We can use [rem/2](https://hexdocs.pm/elixir/Kernel.html#rem/2), the reminder of an integer division. The first argument is our index (*dividend*) and the second is the length of the list (*divisor*)

```elixir
iex> rem(2, 3)
2
iex> rem(3, 3)
0
iex> rem(4, 3)
1
iex> rem(-4, 3)
1

iex> ["a", "b", "c"] |> Enum.at(rem(2, 3))
"c"
iex> ["a", "b", "c"] |> Enum.at(rem(3, 3))
"a"
iex> ["a", "b", "c"] |> Enum.at(rem(4, 3))
"b"
```

So we can use it to loop over the list, while the index increments.

Since our image list is *constant*, we can define it as [module attribute](https://elixir-lang.org/getting-started/module-attributes.html#as-constants) and calculate its length at compilation time

```elixir
defmodule GalleryWeb.GalleryLive do
  @images [
    "https://images.unsplash.com/photo-1562971179-4ad6903a7ed6?h=500&fit=crop",
    "https://images.unsplash.com/photo-1552673597-e3cd6747a996?h=500&fit=crop",
    "https://images.unsplash.com/photo-1561133036-61a7ed56b424?h=500&fit=crop",
    "https://images.unsplash.com/photo-1530717449302-271006cdc1bf?h=500&fit=crop"
  ]
	@images_count Enum.count(@images)
  ...

	def image(idx) do
		idx = rem(idx, @images_count)
		Enum.at(@images, idx)
	end
end
```

Using [Enum.at](https://hexdocs.pm/elixir/Enum.html#at/3) in this case is more than fine, but remember that lists in Elixir are linked lists: if the list is big, `Enum.at/2` can become expensive since it has to go through the whole list to reach the elements at the end.

*Please drop me a comment below if you'd like to have an episode on how lists work in Elixir.*

We can define an `img` tag, in the view in `render/1` function, and use the `image/1` function to set the `src` attribute's value.

```elixir
def render(assigns) do
  ~L"""
  <label>Image Index: <%= @idx %></label>
  <button phx-click="prev">Prev</button>
  <button phx-click="next">Next</button>
	
  <img src="<%= image(@idx) %>">
  """
end
```


Going back to the browser, after refreshing the page, now we should see a working first version of our gallery - by pressing *prev and next* buttons we go through the images. 🎉🥳

![Working first version](https://www.poeticoding.com/wp-content/uploads/2019/11/3_working_first_version.gif)

## A bit of refactoring: `Gallery` module

The next step is to do a bit of refactoring, which will make easier to add thumbnails and a slideshow functionality.

At the moment we have everything in the `GalleryWeb.GalleryLive` module - it would be nice to uncouple the gallery logic from the LiveView part, moving it to a different module called `Gallery`, defined in `lib/gallery.ex`

```elixir
# lib/gallery.ex
defmodule Gallery do
  # image_ids()
	# first_id()


  # prev_image_id(ids, id)
  # prev_index(ids, id)

	# next_image_id(ids, id)
  # next_index(ids, id)

  # thumb_url(id)
  # large_url(id)

	# image_url(image_id, params)
end
```



## `image_url/1`

The first function we are going to write is `image_url/1`. If we take a closer look at one of the Unsplash's image URL, we see that it's made by different parts

- `"https://images.unsplash.com/"` the Unsplash base url
- `"photo-1562971179-4ad6903a7ed6"` the image id
- `"?h=500&fit=crop"` and some query params. We can use the query params to request a different image size: tuning `h` and `w` params we are able to request a large image or a thumbnail

Instead of keeping the list of URLs of large images, we can switch to a list of images ids. With just the image id we can build a URL for both thumbnails and large images.

```elixir
# lib/gallery.ex

defmodule Gallery do
	@unsplash_url "https://images.unsplash.com"

	@ids [
    "photo-1562971179-4ad6903a7ed6",
    "photo-1552673597-e3cd6747a996",
    "photo-1561133036-61a7ed56b424",
    "photo-1530717449302-271006cdc1bf"
  ]

	def image_ids, do: @ids

  def image_url(image_id, params) do
    URI.parse(@unsplash_url)
    |> URI.merge(image_id)
    |> Map.put(:query, URI.encode_query(params))
    |> URI.to_string()
  end

end
```

We use the [URI](https://hexdocs.pm/elixir/URI.html) module to compose and generate the final URL. `URI.parse/1` parses the 
Unsplash base url returning a URI struct, then `URI.merge/2` sets the `image_id`  as the `path`

```elixir
%URI{
  scheme: "https",
  port: 443,
  host: "[images.unsplash.com](http://images.unsplash.com/)",
  path: "/photo-1561133036-61a7ed56b424",
  query: nil,
	...
}
```

`URI.encode_query/1` then encodes a `params` map to a query string. If we want a thumbnail we can set both `w` and `h` to 100px

```elixir
iex> URI.encode_query(%{w: 100, h: 100, fit: "crop"})
"fit=crop&h=100&w=100"
```

`URI.to_string/1` returns the final URL, converting the struct into a string.

```elixir
iex> Gallery.image_url("photo-1562971179-4ad6903a7ed6", %{w: 100, h: 100})

"https://images.unsplash.com/photo-1562971179-4ad6903a7ed6?h=100&w=100"
```

Let's add two helpers which can be useful later

```elixir
# lib/gallery.ex

defmodule Gallery do
  
	def thumb_url(id), 
		do: image_url(id, %{w: 100, h: 100, fit: "crop"})
	
	def large_url(id), 
		do: image_url(id, %{h: 500, fit: "crop"})

  ...

end
```

`thumb_url/1` returns a 100x100 image url and `large_url/2` a 500px height image.

### `next_image_id/2` and `prev_image_id/2`

Instead of dealing with an index directly (incrementing and decrementing it), we define a  `next_image_id(ids, id)` function that given an `id`, returns the **next** element in the `ids` list.


```elixir
# lib/gallery.ex

defmodule Gallery do
  
	def first_id(ids \\ @ids), do: List.first(ids)
	
	
	def next_image_id(ids\\@ids, id) do
		Enum.at(ids, next_index(ids, id), first_id(ids))
	end
	
	
	defp next_index(ids, id) do
	  ids
	  |> Enum.find_index(& &1 == id)
	  |> Kernel.+(1)
	end
	
  ...
end
```


`next_image_id` function has two different arity: 

- `next_image_id/1`: passing only the `id` argument, `ids` will be equal `@ids`.
- `next_image_id/2`: with this function we pass the `ids` list ourself, which can be useful to unit test the function.

`next_image_id/2` uses the private function `next_index(ids,id)`, which **finds the index** of the `id` element in the `ids` list, **incrementing it** by `1`. 

If the `id` is the last element in the list, `next_index/2`  returns an index that is *out-of-bound* and `Enum.at/2` (in `next_image_id/2`) would return `nil`. 

We can pass  `first_id(ids)` as `Enum.at/3` third argument - instead of returning `nil`, `Enum.at/3` will return the first element in `ids`.

Let's see `next_image_id/2` in action on the terminal

```elixir
iex> Gallery.next_image_id(["a", "b", "c"], "b")
"c"
iex> Gallery.next_image_id(["a", "b", "c"], "c")
"a"
```

`prev_image_id/2` and `prev_index/2` are really similar to the `next_*` functions

```elixir
# lib/gallery.ex

defmodule Gallery do

	def prev_image_id(ids\\@ids, id) do
		Enum.at(ids, next_index(ids, id))
	end
	
	
	defp prev_index(ids, id) do
	  ids
	  |> Enum.find_index(& &1 == id)
	  |> Kernel.-(1)
	end
	
  ...

end
```

In the *prev_* case we don't need to set the Enum.at/3 default value, because prev_index/2 doesn't return an index *out-of-bound*. When `id` is the first element of `ids`, `prev_index/2` passes the `-1` index to `Enum.at/2`, which returns the `ids` last element.

## Refactoring `GalleryLive`

Now it's time to make some changes in `GalleryLive` and use the functions we've just built in `Gallery`.

We don't need anymore the  `@images`, `@images_count` module attributes and `image/1` function.

In `mount/2`, instead of `:idx`, we now assign  `Gallery.first_id()` to `:current_id`

```elixir
# lib/gallery_web/live/gallery_live.ex

defmodule GalleryWeb.GalleryLive do
  ...

	def mount(_session, socket) do
	  {:ok, assign(:current_id, Gallery.first_id())}
	end
	
  ...
end
```

In  `render/1` we use `@current_id` and instead of `image(@idx)` we adopt `Gallery.large_url(@current_id)`

```elixir
# GalleryWeb.GalleryLive
# lib/gallery_web/live/gallery_live.ex

def render(assigns) do
	~L"""
	<label>Image id: <%= @current_id %></label>
  <button phx-click="prev">Prev</button>
  <button phx-click="next">Next</button>
  
  <img src="<%= Gallery.large_url(@current_id) %>">
  """
end
```


Then we replace `assign_prev_idx/1` and `assign_next_idx/1` with `assign_prev_id/1` and `assign_next_id/1`, updating `handle_event("prev", _, socket)` and `handle_event("next", _, socket)` accordingly

```elixir
# GalleryWeb.GalleryLive
# lib/gallery_web/live/gallery_live.ex

def handle_event("prev", _, socket) do
  {:noreply, assign_prev_id(socket)}
end

def handle_event("next", _, socket) do
  {:noreply, assign_next_id(socket)}
end

def assign_prev_id(socket) do
  assign(socket, :current_id, 
    Gallery.prev_image_id(socket.assigns.current_id))
end

def assign_next_id(socket) do
  assign(socket, :current_id, 
    Gallery.next_image_id(socket.assigns.current_id))
end
```

Refreshing the page on the browser we should get a similar result of what we got before, but this time, instead of an index, we use image ids.


![Gallery with image id](https://www.poeticoding.com/wp-content/uploads/2019/11/4_browser_with_image_id.jpg)

## Thumbnails

It's now really easy to add the thumbnails, using a [comprehension](https://elixir-lang.org/getting-started/comprehensions.html) that maps `id`s returned by `Gallery.image_ids()` to `<img>` tags.  We use `Gallery.thumb_url/1` to get a thumbnail url

```html
<center>
  <%= for id <- Gallery.image_ids() do %>
    <img src="<%= Gallery.thumb_url(id) %>">
  <% end %>
</center>
```

![Thumbnails](https://www.poeticoding.com/wp-content/uploads/2019/11/5_thumbnails.jpg)

It would be nice to see which of the images in the thumbnails is shown below. 

```elixir
# GalleryWeb.GalleryLive
# lib/gallery_web/live/gallery_live.ex

defp thumb_css_class(thumb_id, current_id) do
  if thumb_id == current_id do
    "thumb-selected"
  else
    "thumb-unselected"
  end
end
```

    

We write the `thumb_css_class(thumb_id, current_id)` helper into `GalleryLive` and use it to render the css class of the thumbnails `img` tag.

```html
<%= for id <- Gallery.image_ids() do %>
	<img src="<%= Gallery.thumb_url(id) %>"
  class="<%= thumb_css_class(id, @current_id) %>">
<% end %>
```

`thumb_css_class/2` returns `"thumb-selected"` css class when `id` and `@current_id` are equal, `"thumb-unselected"` otherwise.

Then we add the two css classes in `assets/css/app.css` 

```css
/* assets/css/app.css */

.thumb-selected {
  border: 4px solid #0069d9;
}

.thumb-unselected {
  opacity: 0.5;
}
```

![Selected Thumbnails](https://www.poeticoding.com/wp-content/uploads/2019/11/6_selected_thumbnails.gif)

## Slideshow

With a *slideshow* feature we want that `GalleryLive`  automatically changes the current image at a regular interval.

Let's start by assigning a `:slideshow` value in `mount/2`, initially set to `:stopped`.

```elixir
def mount(_session, socket) do
  socket = 
    socket
    |> assign(:current_id, Gallery.first_id())
    |> assign(:slideshow, :stopped)
  {:ok, socket}
end
```

Then we change the view in `render/1` by removing the `<label>` tag and adding a third button: 

- when `@slideshow` is `:stopped` we show a *Play* button, which sends a `"play_slideshow"` event when clicked
- otherwise we show a *Stop* button, which sends a `"stop_slideshow"` event

```html
<center>
  <button phx-click="prev">Prev</button>
  <button phx-click="next">Next</button>

  <%= if @slideshow == :stopped do %>
    <button phx-click="play_slideshow">Play</button>
  <% else %>
    <button phx-click="stop_slideshow">Stop</button>
  <% end %>
</center>
```



We handle the first event which starts the slideshow

```elixir
def handle_event("play_slideshow", _, socket) do
  {:ok, ref} = :timer.send_interval(1_000, self(), :slideshow_next)
  {:noreply, assign(socket, :slideshow, ref)}
end

def handle_info(:slideshow_next, socket) do
  {:noreply, assign_next_id(socket)}
end
```

`:timer.send_interval(milliseconds, pid, message)` starts the slideshow by sending every second a `:slideshow_next` message to `self()`, the Gallery LiveView process. It returns a `ref` reference, which we'll need later to stop the slideshow, and we assign it to `:slideshow`.

The process now receives a `:slideshow_next` message every second. This message is handled by `handle_info(:slideshow_next, socket)` which calls `assign_next_id(socket)` to assign `:current_id` to the next image id.


To stop the slideshow, we implement the `handle_event("stop_slideshow", _, socket)` function that cancels the timer and assigns `:slideshow` back to `:stopped`.

```elixir
def handle_event("stop_slideshow", _, socket) do
  :timer.cancel(socket.assigns.slideshow)
  {:noreply, assign(socket, :slideshow, :stopped)}
end
```


![Gallery with Slideshow](https://www.poeticoding.com/wp-content/uploads/2019/11/0_final_result.gif)


We made it! 🎉👩‍💻👨‍💻🎉  We finally got a slideshow feature that shows us the images automatically!

## What's next?

We've seen a lot! If you want to copy & paste the code, you find the [full code at this gist link](https://gist.github.com/alvises/2044a0ababef92d7d0b923a678e0194e) with the two `Gallery` and `GalleryWeb.GalleryLive` modules we've built during the article. 

If you want the plug&play project's code (to try everything out without coding it yourself) please let me know in the comments below.

You can now take a step forward: you can use [LiveView live_link and pushState support](https://www.poeticoding.com/phoenix-liveview-change-the-url-without-refreshing-the-page/)  to bring the image id in the URL and update the URL when showing a different image. This gives you a great way to share a specific image to other users. 


**[Subscribe to get FREE Tutorials by email](https://pages.convertkit.com/c021826e86/5ddcb20a27)**. Start learning Elixir and Phoenix to build features and apps!