Skip to content
This repository has been archived by the owner on Apr 25, 2024. It is now read-only.

Add reactivity #25

Open
BartAdv opened this issue Mar 13, 2014 · 8 comments
Open

Add reactivity #25

BartAdv opened this issue Mar 13, 2014 · 8 comments

Comments

@BartAdv
Copy link

BartAdv commented Mar 13, 2014

Hi,

So I've been experimenting with references and watches, and I came with idea of a trait that allows you to hook to the reference and alter the widget when reference value changes:

(deftrait :ref-text [^android.widget.TextView wdg {:keys [ref-text]}]
  (add-watch ref-text nil
             (fn [_ _ o n] (post wdg (.setText wdg n)))))

This helped me to refrain from fetching widgets by ids/vars and just allowed to focus on the values prepared for them:

(def txt (atom nil))

(def ui [:linear-layout {:id-holder true}
         [:text-view {:text "Hello from Clojure!!!"}]
         [:edit-text {:text "Initial text" :ref-text txt}]])

(reset! txt "Some other text")

There is one issue here: I haven't hooked to the widget destroy event to release the reference that watcher holds (I haven't yet found the way to do it, am not very knowledgeable in android SDK ;) ), but I'm pretty sure it can easily be done.

Real question is, do you think it would be feasible to alter existing traits to work this way if the value specified is a reference (just additional case)? Many traits would benefit from such thing, and it would give neko that 'reactive' feel (possibility of using dataflow programming a'la javelin for example, is so tempting). Or whether it's better as an 'add-on' (like I did in my code)?

@BartAdv
Copy link
Author

BartAdv commented Mar 13, 2014

I think weak references could help here - watcher' callback wouldn't capture direct reference to widget, and would just do nothing when it's null (possibly uninstall itself)

@BartAdv
Copy link
Author

BartAdv commented Mar 14, 2014

Started experimenting here: BartAdv@8a6c5b0

@alexander-yakushev
Copy link

Hello Bartosz,

This certainly looks interesting. Possibilities for reactive programming were requested a couple of times from people trying Clojure/Android. I myself think this is a great way to go. Although I'm still not sure if Neko should enforce this kind of UI architecture. This approach needs evaluation to see if all the attributes can be described this way, will there be much performance overhead, can the ordinary traits and reactive traits coexist. I encourage you to continue your experiment and see what can come out of it.

I'm leaving this issue open as work in progress. Thanks for your efforts and hope you enjoy CoA!

@ghost
Copy link

ghost commented Apr 1, 2014

Just to add my $.02:

I've been working on a (by now) quite substantially-sized app, and had non-obvious references to UI components fall on my feet more than once. Besides, attaching UI elements directly to unfiltered application state only ever works at the beginning... until something flickers ... or gets to large for the widget size ... or is nil ... ;-)

What seems to work well for now, is to keep state local, and publish updates via event buses (realized trivially with core.async and pubs), to which my activities or fragments subscribe in onResume, and from which they unsubscribe in onPause. The subscriptions themselves are kept in an atom that is local to the Activity/Fragment (following the same pattern as in for example Nightweb), so even if I forget to unsubscribe, all references to views that may be kept by the subscribers are going down with the containing object.

@alexander-yakushev
Copy link

@vschlecht Can you please take a look at the solution here #23 (last comment)? It is not reactive, but at least it is not locally-bound and should be much safer than keeping references in namespace vars.

@ghost
Copy link

ghost commented Apr 1, 2014

Honestly, I fail to see the point of using (*a) in this particular example:
after all the reference to the Activity is available directly as "this" in the on-create fn.

What I really like about (*a) is that it's quite an elegant solution to be used in REPL development that probably won't break things just by remaining in production code in the same way that :def a did.

However I do not think that it should be used outside of the REPL for the following reasons:

  • what happens when you keep two activities in the same namespace...? Which one will (*a) deliver at any given time?
  • In situations where use of (_a) would be more convenient because the activity is complicated to get using the Android way, can you rely on (_a) delivering a non-nil result at all?
  • If (*a) delivers a result, can you always be certain that it references the particular instance of the activity that your component is attached to?

IMHO by design, the use of (*a) will pollute every function that uses it with system-mutable global state.

@alexander-yakushev
Copy link

Actually, I pointed to the example that shows usage of find-view to obtain references to UI widgets. *a was only incidental there, but since you touched that topic we can discuss it as well.

I agree that (*a) is better be used only for REPL development. But it is still better than :def. We should encourage in docs to change calls to (*a) with true activity references, but if users don't the results won't still be as destructive (at least memory-wise).

As for the first point, to distinguish two activities in the same namespace :key attribute was added to defactivity which allows to specify a custom alias for the activity rather than its namespace. Say:

(defactivity foo.bar.BazActivity
  :key :baz
  .....)

(*a :baz) => yields BazActivity instance

@BartAdv
Copy link
Author

BartAdv commented Apr 2, 2014

As for the original point, I had experimented bit more back then, and it quickly became hairy as soon as I tried it to be bound two-way. It's pretty raw, uses quite low level primitives (watchers), and it tries to connect two distinct values: the original value, and the value kept by the widget, which does not map 1:1 (TextViews use entirely different structure than string for example).

That's why it's just unfeasible to add such trivial reactivity on top of neko (which design goals are to sit on top of original SDK), and it would rather require something entirely different.

As for the other point, I haven't yet given it a go.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants