This isn’t a Kotlin-specific question per-say, but...
# announcements
a
This isn’t a Kotlin-specific question per-say, but I am doing this in kotlin. I’m trying to get away from extending classes because of intertwining behavior. I am abstracting my question, so if you want more specific details, ask me. I have a
GUI
which groups together `GUIComponent`s. Each
GUI
has a
onExit()
method that is called when the user tries to exit the
GUI
. • suppose I have
GUI
with a
counter
and every time a user
GUIComponent
is (A) clicked or the (B) user exits a
GUI
the
counter
should be increased. Once
counter
reached a
threshold
,
AnEvent
occurs how should I structure this? WAY 1 Right now I almost might think of having
GUIWithCounter(val counter: Int) : BaseGUI(...)
and each time (A | B) occurs we open
GUIWithCounter(counter+1)
• this hard because there is a lot of logic which goes into the creation of
BaseGUI
… therefore, when creating it we’d want to use a factory method instead. In Kotlin, we can’t extend a class by using factory methods WAY 2 We could have
GUIWithCounter : GUI
(here
GUI
is an interface) … we’d create an instance of
BaseGUI
with factory methods with desired arguments… and then use strategy pattern (delegate
GUI
methods to the instance of
BaseGUI
) .. this seems nice and dandy but here we’d need
BaseGUI
to be a concrete class. Therefore, we’d need a
onExit()
function in
BaseGUI
that does nothing/throws
UnimplementedException
(probably not an issue… but this is obv bad code. Perhaps
BaseGUI
would instead take a
GUIEventHandler
interface in the constructor and delegate
onExit()
to implementation defined there. Maybe just me but handlers seem like they could get messy with a bunch of
object: …
in code.) WAY 3 I don’t think this is a good idea???… but we could also have a
GUICounterState
and have a function which creates a
BaseGUI
with a handler and components that increment the
GUICounterState
. The
GUICounterState
would then have a detection method to see when
counter
has gone over a specific threshold. Thoughts?
h
I would definitly go for passing an eventbus or callback handler into the gui components. It's each components job to post the received exit event there. The counter state could then be completely Independent of the gui as well and check on its own for count > threshold. You also don't need to add interfaces or anything for that, you can even ditch the onExit methods and have some private lambdas do the job, once the bus or handler is available in the components
👍 1
a
I am thinking way 1
basically way 1 = function to return GUI vs way 2 = strategy pattern-ish
h
Not too easy to answer, you can combine both and pass a handler/bus instead of the counter, but i don't think it's needed or makes things too much better. Go for 2 :)
a
ok … is this what you were thinking? I think this is the best way… maybe at least… https://gist.github.com/andrewgazelka/232a69f1022e34b9568eef61d3cedb7c
oh this way is also cool since I can still do
instanceof
with the handler
(which I need to do)
h
I would not resort to instanceof with the handler. Either you have a typed handler and know what functions it has, or an eventbus with unknown event types is probably a better way, because then you seem to want uncoupled events and consumers and producers. Don't do that unless you need to, loose coupling costs simple function calls here :) I would do it slightly different than your current approach though. You don't need to pass in the attempt counter into your guis i think. The holder for the value could be a simple Independent listener that has it's state private. Depending on what you want to trigger on attempts >= maxcount, this would be better.
j
You could use an eventbus but that may be overkill unless you are using it for multiple reasons. The easiest way to decouple your concerns is using an observer pattern. https://www.baeldung.com/kotlin/observer-pattern
a
@Hanno @Jakub Pi the issue is my GUI is a CAPTCHA solver that kicks people out after 30 seconds. There is a global timer (which is called every second) which checks all open GUIs and currently has
instanceof
on
CaptchaHandlers
to see which GUIS are Captchas. You are suggesting that I have a scheduler for each Captcha that I can subscribe to—even though it might be more efficient than having one giant one?
Copy code
val currentGUI = GUIPathStore[onlinePlayer].current?.handler as? CaptchaHandler ?: continue
        if (millis - currentGUI.startedTime < 30_000) continue
        onlinePlayer.kickPlayer("You must solve the captcha within 30 seconds")
h
Hmmm... It's not easy for me to understand what your concern is. So lets take a step back. You have a component that needs a list of your gui components for checking if they are open for more than 30 Seconds. Sounds like this component should indeed be some kind of factory and all other instantiations of your components should be prevented. No instance of anymore needed then. Then your factory passes a state wrapper instance that holds your try counter into your components. Your single scheduled check could then iterate the components list and check for 30 seconds, and check their counter state as well. The only thing your components are left to do is call those two functions on the handler on captcha try or on exit calls.
j
If you use something lightweight (like coroutines) the number won't really be an issue. So as long as you don't create a new thread for each instance you should be fine from an efficiency standpoint. Best is that you can cancel the timer when the GUI component is closed by tying it to the lifecycle of the component. You are abusing the type system if you try to create a hierarchy of classes to handle your specific problem. Prefer composition over inheritance.
a
ok I think I finally got a good design (using coroutines). Thoughts? @Jakub Pi @Hanno
and then it would respond to another APIs events like so
I tried to make code as clean as possible but basically way it works is there are
ITEM_COUNT
items and only one is right. The solver must click on the right item within
30
seconds or they will get kicked
h
All an all nice code, minor naming things are confusing but that might be more my problem than one of your code. What i still find a bit strange is how you get your events out of the gui xD
a
@Hanno yeah… I am trying to debate whether the way I got events out is weird or sexy… 🤷🏼‍♂️ I mean I think it works well?
image.png
j
This is a much clearer separation of concerns. I don't quite understand your randomization in the helper. Are you mixing test code with source code? Also, the way you are evaluating the flow makes it more of a pull instead of a push. You may experience some weirdness.. Flows re-evaluate the source each time they process, and generally run cold. For a gui application you generally run push/hot.
a
@Jakub Pi well I am using a hot flow since I am using
BroadcastChannel#asFlow()
imo they are better than
ReceiveChannels
because there are a lot of predefined extension funcs for them
ignore the whole randomization part
it is just a captcha and that is just randomizing items hahaha
j
It's a much cleaner design. Can't speak about coroutines / flow as I've not used them extensively. Only thing I can say is watch your channel capacity, you will get into trouble once you go async and have multiple people using the catchpa at once.
a
@Jakub Pi yup BroadcastChannels are special tho (compared to regular Channels) https://github.com/Kotlin/kotlinx.coroutines/issues/1082
h
Ah, for some reason i missed the channel part, my bad! Nice code indeed all in all, i would be pleased as your coworker. Props for the whole discussion here.
👍 2