The Task
Brace yourselves, wall of text is coming
For my current project I have a need to show notifications and small indicators (to highlight areas with new information) to users. Most of these indicators or notifications should be available/visible on any page as long as the user is logged in.
Why a discussion?
This is probably a very common thing to have/need, but searches in the forum turned up pretty old or unrelated posts. Maybe I was not good at using the search function.
I would be very interested in how you people would tackle (or have tackled) a scenario like this. While the motivation behind this post is to answer the question on how to do this for my project, I am aiming to have more of a discussion than just asking a question.
What do we need to do?
Assuming both an indicator and a popup notification are desired, it is necessary to
- Figure out if there are unread notifications on page load to display indicators and
- Subscribe and react to updates on every page for continuous updates
Out goals?
- Hit the database(s) as rarely as possible
- Manage as little state as possible - or better: none
- Duplicate as little code as possible
- Do not touch each LiveView file
- Keep the concept simple
Discussion: How can we do that?
Assuming all pages are LiveViews.
This is the part that I would really like to discuss.
The following outlines some possible solutions I could come up with (some of them I have never tried) and what I think the upsides and downsides are, but I might be mistaken there, in which case I would be happy to improve my understanding.
Page load
Simple/Stupid: Hit the database on every load
Simple to set up: slap the database request into a Plug or on_mount and assign the result. Use the assign in your template.
No state management
Does not scale well as it requires unnecessary resources ā¦ although most applications would probably be okay with this.
Makes the page feel less snappy or even slow.
Not that much of an argument, but: Somehow feels bad to do it like this :P.
Is there a way to keep some of the data when navigating within a live_session?
Go through a cache (Agent/GenServer/ETS) to get the the database result
When a user is first active after some time, grab their data from a database, store it somewhere and do incremental updates
Pretty simple to set up (on_mount)
Faster after first load
Trades computing for memory
Possible bottleneck?
State management: The cache needs to subscribe to updates and invalidate its data if needed. Not clear how long we should wait before data can be dropped and probably not trivial to keep state up to date?
Start a User twin that lives as long as the User is active (Presence)
Basically the same as the above, but with
better isolation and less bottleneck-y?
When using e.g. Phoenix Presence we have a clear indicator on when we can drop the twin and its data.
Nested, sticky LiveView
Kind of like the twin: Separate process, that fetches the data on initial load and can then act as a cache.
Requires no lifecycle handling, as Phoenix would cover that
Only useful when navigating within a live_session(?), as the nested LiveView would otherwise be reloaded as well.
For each new window or browser tab we start from scratch
If communication with the parent LiveView is needed, for example to update different indicators on the page, we actually do not gain much, as we still have to touch every LiveView? ā Could make use of JS events here.
Live Updates
Subscribe each LiveView to a PubSub
Pretty simply concept
Lots of code copying for duplicate handle_info()
and mount()
content.
Subscribe on_mount, add handle_info() to use XYZ, :live_view
Pretty simple setup
Compiler will complain that handle_info()
functions are not grouped.
Move handle_info() to separate macro/quote, place it manually next to other handle_info()
Compare to above:
Compiler is fine
Need to add code to each LiveView
Nested, sticky LiveView
Basically like the player in LiveBeats Ā· Phoenix Framework Have a separate LiveView that fetches the
No copied code: Put all handle_info()
and subscriptions in one place
Simple setup: Add the LiveView to your template
Popup notifications should be simple on each page with this through e.g. absolute positioned UI elements.
Only useful when navigating within a live_session(?), as the nested LiveView would otherwise be reloaded as well.
If communication with the parent LiveView is needed, we actually do not gain much, as we still have to touch every LiveView.
JavaScript event listener in app.js
Not entirely sure about this one, as I avoid JS wherever possible. Maybe an option is to add a JS event listener to app.js and then update specific DOM elements as needed (e.g. giving notification indicators an ID and show/hide them).
Should also work with dead views.
Separate Phoenix Channel
I have no experience with this.
Should also work with dead views.
Bonus Question/Scenario
If some of the pages users can visit (and have notifications on) are also publicly available pages and therefore can be viewed without having an actual user associated, how would you suggest to handle that? Separate LiveView? Separate Template?
for everyone who got here without skiping