Mobile app development with LiveView Native and Elixir. Part - 2
Tags: liveviewnative, elixir, mobile, phoenix
Published: 2024-07-01 13:37:42

Hi all,

This week, I have come up with the exciting new blog in which I am going to explain you how to handle state in mobile application which we are building with the help of LiveView Native by Dockyard.

In my earlier blog, I have explained how to setup the LiveView Native project, We are going to continue from the same step from where we have left it.

If you have not gone through that blog, please check it out here in the following link.

```
https://dev.to/rushikeshpandit/mobile-app-development-with-liveview-native-and-elixir-4f79
```

Let's start writing the actual code for counter example.

Navigate to `lib/native_demo_web/live/` directory and change the code of home_live.swiftui.ex as shown below. 

`home_live.swiftui.ex`


```
defmodule NativeDemoWeb.HomeLive.SwiftUI do
  use NativeDemoNative, [:render_component, format: :swiftui]

  def render(assigns, _interface) do
    ~LVN"""
    <VStack id="hello-ios">
      <HStack>
        <Text class={["bold(true)"]} >Hello iOS!</Text>
      </HStack>
      <HStack>
        <.link  navigate={"/counter"} >
          <Text>Counter Demo</Text>
        </.link>
      </HStack>
    </VStack>
    """
  end
end

```

Also, replace the `render` method of `home_live.ex` with below code.

```
def render(assigns) do
    ~H"""
    <div>
      Hello from Web <br />
      <br />
      <button
        phx-click="navigate"
        class="text-stone-100 bg-indigo-600 font-semibold rounded py-2.5 px-3 border border-indigo-600 transition hover:bg-indigo-700"
      >
        <.link href={~p"/counter"}>Go to counter example</.link>
      </button>
    </div>
    """
  end
```
 
In above code, we have added `link` component in mobile app and `button` component in web app, which will navigate user to `/counter` route.


Now, navigate to `lib/native_demo/` and create a new file with name `counter.ex` and paste the  following content.

`counter.ex`


```
defmodule NativeDemo.Counter do
  use GenServer

  alias __MODULE__, as: Counter

  @initial_state %{count: 0, subscribers: []}

  # Client

  def start_link(_initial_state) do
    GenServer.start_link(Counter, @initial_state, name: Counter)
  end

  def increment_count do
    GenServer.call(Counter, :increment_count)
  end

  def get_count do
    GenServer.call(Counter, :get_count)
  end

  def join(pid) do
    GenServer.call(Counter, {:join, pid})
  end

  def leave(pid) do
    GenServer.call(Counter, {:leave, pid})
  end

  # Server (callbacks)

  def init(initial_state) do
    {:ok, initial_state}
  end

  def handle_call(:increment_count, _from, %{subscribers: subscribers} = state) do
    new_count = state.count + 1
    new_state = %{state | count: new_count}

    notify_subscribers(subscribers, new_count)

    {:reply, :ok, new_state}
  end

  def handle_call(:get_count, _from, state) do
    {:reply, state.count, state}
  end

  def handle_call({:join, pid}, _from, state) do
    Process.monitor(pid)

    {:reply, :ok, %{state | subscribers: [pid | state.subscribers]}}
  end

  def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do
    {:noreply, %{state | subscribers: Enum.reject(state.subscribers, &(&1 == pid))}}
  end

  # Private functions

  defp notify_subscribers(subscribers, count) do
    Enum.each(subscribers, fn pid -> send(pid, {:count_changed, count}) end)
  end
end

```

In above file, we are starting a GenServer with the initial state as `%{count: 0}`. Also we are have written couple of methods such as `increment_count`, `get_count` which eventually gets handled by GenServer and changes will be notified to all the subscribers.

You can read more about GenServer in the following link.

```
https://dev.to/rushikeshpandit/demystifying-elixir-genservers-building-resilient-concurrency-in-elixir-9jm
```

As we have written GenServer, next step is to start it. To do this, we have to navigate to `application.ex` which is inside same directory and over there we need to find `def start(_type, _args)` method and add `NativeDemo.Counter` to the children's array. Start method should look something like this.


![application.ex changes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hz6l37fi5fme2ur43oxm.png)


Now, navigate back to `lib/native_demo_web/live/` and create 2 files with name `counter_live.ex` and `counter_live.swiftui.ex`

`counter_live.ex`


```
defmodule NativeDemoWeb.CounterLive do
  use NativeDemoWeb, :live_view

  use LiveViewNative.LiveView,
    formats: [:swiftui],
    layouts: [
      swiftui: {NativeDemoWeb.Layouts.SwiftUI, :app}
    ]

  alias NativeDemo.Counter

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <div class="text-slate-800 bg-slate-50 content-center items-center text-center">
        <.back navigate={~p"/home"}>Back to Home</.back>
        <div class="mb-2.5">This button has been clicked <%= @count %> times.</div>
        <div>
          <button
            phx-click="increment-count"
            class="text-stone-100 bg-indigo-600 font-semibold rounded py-2.5 px-3 border border-indigo-600 transition hover:bg-indigo-700"
          >
            <span>Click me</span>
          </button>
        </div>
      </div>
    </div>
    """
  end

  @impl true
  def mount(_params, _session, socket) do
    Counter.join(self())

    {:ok, assign(socket, :count, Counter.get_count())}
  end

  @impl true
  def handle_info({:count_changed, count}, socket) do
    {:noreply, assign(socket, :count, count)}
  end

  @impl true
  def handle_event("increment-count", _params, socket) do
    NativeDemo.Counter.increment_count()

    {:noreply, socket}
  end
end
```

`counter_live.swiftui.ex`

```
defmodule NativeDemoWeb.CounterLive.SwiftUI do
  use NativeDemoNative, [:render_component, format: :swiftui]

  def render(assigns, _interface) do
    ~LVN"""
    <.header>
      Counter
    </.header>
      <HStack>
        <Text class={["bold(true)"]}>This button has been pressed <%= @count %> times.</Text>
      </HStack>
      <HStack>
        <Button phx-click="increment-count">
          <Text >Press me</Text>
        </Button>
      </HStack>
    """
  end
end
```

Explanation for  above code.

In the `mount` method of `counter_live.ex`, we are adding our live view process to GenServer and getting the value of count. and in the `render(assigns)` method, we have a button with the `phx-click` which sends an event with name `increment-count`. In the `handle_event` method, we are incrementing the count by one. Our GenServer then notify the all subscriber with the event `count_changed`. Once our Live view gets this event, `handle_info` comes in actions and update the count which was incremented by the server. This happens in case of live view web.

Mobile app is pretty straightforward. In `counter_live.swiftui.ex`
mobile app is showing only value of count which is added into socket by webview and there is one button with the `phx-click` which sends an event with name `increment-count`. That's it. Rest all is taken by live view.


Once you done with all the changes mentioned above, head to the `router.ex` which is inside `lib/native_demo_web` directory and add following line.

```
live "/counter", CounterLive
```

Your router file should look something like this.


![router.ex](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kz6bwl9pb117dhs2b49v.png)

Now, run the application using `iex -S mix phx.server` and hit `http://localhost:4000/counter` on the browser.

You will see the following.

![Counter web image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/soqib6tsuq8g21q2lyl3.png)

Now, open `native/swiftui/NativeDemo.xcodeproj` using xcode and try to run the application,

you should be able to see the following.



![Counter page home](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpswr12ahnmj7tzktl2s.png)

Tap on `Counter Demo` button. It will navigate you to next page as shown below.


![Counter page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rq13rynraf9sqz05693w.png)


If you are able to see this, then Congratulations!!!

Now, lets see the live view native in action.



![Live view in action](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ff7rxnkoyfsyjed48n36.gif)

# Congratulations!!!

You have successfully created and setup GenServer utilize it into mobile app to manage the state.


You can find sample code on [GitHub](https://github.com/rushikeshpandit/live_view_native_demo)


In case if you face any issue, try deleting `_build` directory and compile code again using `mix compile` command.

If you have any suggestions/doubts or stuck somewhere in this process, feel free to reach to me via one of the following method.

LinkedIn : https://www.linkedin.com/in/rushikesh-pandit-646834100/
GitHub : https://github.com/rushikeshpandit
Portfolio : https://www.rushikeshpandit.in

In my next blog, I will be try to add some styles to the mobile app and also try to cover some more concepts.

Stay tuned!!!

# #myelixirstatus , #liveviewnative , #dockyard , #elixir , #phoenixframework