In particular, let's say @e
can be either a cat?
or a dog?
after something like
(struct cat [meows] #:transparent)
(struct dog [woofs] #:transparent)
And let's say we have two views cat-view
and dog-view
that expect (obs/c cat?)
and (obs/c dog?)` respectively.
These are simple, and it would be easy to write a single view that mapped values into one of the struct's fields and then did something else, but imagine a much more complicated disparity (say, human game players and non-human played monster groups, with very different internal structures and views).
if-view
and derived friends like cond-view
immediately evaluate their sub-views, so they aren't as useful for heterogeneous data (the views that aren't being used will often still raise exceptions and break things).
I have found dyn-view
, but it is tricky to use correctly, if I understand my problem correctly. The idea would be
(dyn-view @e
(lambda (e)
(cond
[(cat? e) (cat-view @e)]
[(dog? e) (dog-view @e)])))
since the following won't work
(cond-view
[(~> @e cat?) (cat-view @e)]
[(~> @e dog?) (dog-view @e)])
So far this all seems fine, but I have the following problem: if cat-view
or dog-view
create their own internal state for updating parts of the view (say, a tabs
view with a selection), that internal state can lead to bugs in the application.
Why? It has taken me some time to reason through this, so I could be wrong, but:
- The
dyn-view
only remakes the view if @e
changes, so if the internal state changes the view will not be re-made and therefore won't update (?). This I'm less sure about, but it's what I am observing in practice. That is, I can update the internal state and see it propagated to relevant observables, but sub-views are not correctly updated.
- Because the
dyn-view
recreates the entire view if @e
does change, it also resets any internal state. For example, a tab selection would be reset. This makes sense, because it re-creates the internal state after throwing away the old one, since we are calling the function to create the view anew, and that function creates the internal state.
In my actual situation, I have a observable list of (cons/c id? (or/c cat? dog?))
(though again the structures are more complicated and not really unifiable), and I want to use dyn-view
to build the entry-views for each item in the list (because of the issue with if-view
I mentioned above). But the entry-view for one of the types (say dog?
) of things in the list does maintain such internal state (recall the monster example we discussed, where the selection was internal to the view). Both types of entry-view allow callbacks; the actually used callbacks update the observable list by (functionally) modifying the corresponding entry in some way; this causes that entry to be remade by the dyn-view
IIUC. For the cat?
things, everything works pretty normally. For the dog?
things, changing tabs updates the "highlighted" tab, but not the child view (which depends on an observable that is updating, as I have confirmed with obs-observe!
). In addition, interacting with elements that trigger the callbacks which modify the dog?
s does correctly modify them, but the state of that entry-view behaves oddly (usually resetting the highlighted tab to be the first one, as expected based on point 2 above, but without change to the child view as described here).
Without the dyn-view
(say, creating a single (obs/c dog?)
, its dog-view
, and then rendering it in a window) everything works fine.
This is a lot to digest, and I apologize for the lack of straightforward code examples, but it would take me some time to extract useful examples from my actual code. Unfortunately I will also be too busy to respond very much for the next couple of weeks :(
I suppose my question is: I can extract the internal tab-selection state so that the dyn-view
correctly remakes the view when the selection state changes, too, but it's going to be a fair amount of effort. Is that really my only option? Will it solve both problems above?
I think I can solve (2) by having the dyn-view
depend on a combined observable that mixes @e
with the previously internal state, although in the cat?
case that internal state is actually unused.
For (1), though, it's less clear why the sub-views aren't updating when the observables that control them are, so I'm not sure if this would help or not.
An alternate idea might be to have the id
capture both the uniqueness criterion that it does now and the distinction of type; i.e., the new state would be (listof (cons/c (cons/c id? type?) (or/c cat? dog?)))
. The reason is that we have access to the key value in the list-view
callback to make entry-views. I have two issues with this:
- It might work in my current design where, by the time this matters, a particular entry with a particular
id?
will always have the same type?
, but future designs could possibly change that by allowing entries to be added, removed, or edited in some way that might change the type?
.
- It feels error-prone, since I have to maintain state (the
type?
) that effectively already present just by asking if the entry is cat?
or dog?
.
Do you have experience with this kind of heterogeneity and internal state in gui-easy? Am I understanding my problem and my options right? Would my first solution solve the problem, or could there be a bug in the propagation of events to children of dyn-view
(so that maybe the tabs
should be updating but isn't)?