Fork me on GitHub
#membrane
<
2023-03-12
>
Ben Sless11:03:59

On lazy loading and caching remote resources: I have a JSON I pull from an API, and once I get it I can already render text. Each item in that json might also contain a link to a thumbnail. I want to fetch those thumbnails in the background and update the view as they come in, and cache the fetched URLs to not do double work Where do I start? Can an effect handler emit more intents? is that wise? I also want it to not block the UI thread, how can I organize this all in the background?

phronmophobic03:03:26

> I want to fetch those thumbnails in the background and update the view as they come in, and cache the fetched URLs to not do double work > Where do I start? > Can an effect handler emit more intents? If you define an effect handler with defeffect, then you can trigger another effect by calling dispatch!, which is an implicit arg.

(defeffect ::later [$img url]
  (future
    (dispatch! :set $img (download url))))
> how can I organize this all in the background? Just do the work on another thread. Membrane tries to get out of the way here and let you do whatever makes sense for you app with whatever libraries you want.

phronmophobic04:03:53

There's a number of ways to go about avoiding duplicate work: core.async, agents, various caching libraries, etc. The one tip I've picked up is to avoid representing the pending resource with a mutable reference like a promise, future, delay, etc. It's temping to just use a future and check realized? but that mutable reference poisons the otherwise immutable app state and view data. It's ok to use futures, delays, etc. Just make sure your isn't dereferencing or calling an impure function like realized? inside of the view. (As a caveat, delays do mostly work fine in dev, so sometimes I cheat).

phronmophobic04:03:40

I would personally reach for core.async, but I'm not sure if you have a library you like (or don't like). I would happy to provide some sample code if you have a setup your prefer (eg. fewer dependences, core.async, something else).

Ben Sless11:03:06

Another matter, I think I asked about it in the past but I couldn't find the solution, defuis don't compose like I'd expect them. If my app state has keys a b c, and I compose my top level defui from three defui-s which only deal with one element (and set the next), I need to destructure and restructure all of them and pass them to the children ui components, and to also pass the element I set by reference, so when a-view sets b, it has to know b, so it needs to take both a and b as keys. Am I doing something wrong?

phronmophobic04:03:51

I'm not sure I totally follow your example.

phronmophobic04:03:44

Do you have some example code I can look at?

phronmophobic04:03:21

> which only deal with one element (and set the next) What do you mean by deal with one element and set the next?

Ben Sless05:03:01

I'm not sure I want to share the code yet, but if you have some time to pair you can show me what I'm doing wrong or highlight the areas that need ironing out in the API

phronmophobic05:03:48

That would be great!

phronmophobic05:03:10

One quirk about my style is that for ui components, I almost always pass a literal map. For example:

(defui feed-item [{:keys [item]}]
  (let [{:keys [title url]} item]
    (ui/horizontal-layout
     (ui/label title)
     (ui/label url))))

(defui feed [{:keys [feed]}]
  (let [{:keys [items title]} feed]
    (apply
     ui/vertical-layout
     (ui/label title)
     (for [item items]
       (feed-item {:item item})))))


(backens/run (constantly
              (feed {:feed {:title "Title"
                            :items [{:title "a"
                                     :url "<http://a>"}
                                    {:title "b"
                                     :url "<http://b>"}]}})))
So even if the feed and feed-item have keys, I just pass them using a map literal and destructure them inside of the ui component. You can still reference $item, $title, $url, etc. I don't think this is necessarily the best style. I would like to support any style that feels natural unless there's a good reason not to.

phronmophobic05:03:43

I think part of the reason this feels natural to me is that it's pretty common to have a component receive both app state as well as state that is only related to the UI. If you pass in :feed directly, then it's a little harder to separate out the state that is just related to the UI itself.