the problem with nullable listener is that this scenario is never used de facto, so null checks on every call to the listener are useless, and the absense of question marks is a plus to readability. When SomeView created in code we can enforce to pass listener inside of the constructor, but if SomeView created for example by LayoutInflater(android) a late binding is unavoidable and leads to use a single stub noop listener or one stub per every SomeView instance - it’s like a matter of taste but I asked to know opinions of other people
No, there is no guarantee that listener will be set. And I think lateinit in kotlin is a hack because it breaks nullability checks and primarily used for interopability and dependency injection libraries where the guarantee of initialization is on the DI library not user code
Obviously it depends on the context, but most places I’ve used listeners, there was the possibility of having more than one listener for a given object.
And in that case, what you want isn’t a single listener, but a collection* of them.
And of course, that collection can be empty — so no null reference/object/type is needed!
But if there must be only a single listener, then the first implementation with a simple null seems by far the best to me: it’s safe, idiomatic, flexible, easy to read, concise, efficient, and has no issues with initialisation order etc.
(* A set would make sense, but I’ve only seen it done with a list. You probably want something that has low overhead for iteration, and ideally which is thread-safe. Because informing listeners tends to be much more frequent than adding/removing them, CopyOnWriteArray can be a good solution.)
I mean, given his listener design, creating a delegating listener (and hence a linked list of listeners) would be very simple, or even creating a MutableListItemBlahListener. I’d prefer the simple solution of one listener (yes linked lists are slow, but there’s no need to over-complicate the design until necessary
A lot of components usually work with a single controller/handler/processor so in many cases creating a list of listeners/sharedflow is overengineering. I have not seen a button with more than listener to onClick
As I said, it depends upon the context. But back when I did FEs for a living, there were many situations where e.g. an incoming price update could potentially affect many different components.
(And as for complicating a design, CopyOnWriteArrayList has already done most of the heavy lifting, and iterating it is dead easy. Whereas delegation doesn’t easily handle e.g. listener removal without more work — and yes, unless your UI is simple and/or completely static, removing listeners is probably needed too; you wouldn’t want lots of no-longer-visible components hanging around just so they could keep listening for e.g. price updates.)
There is no over-engineering if you use ready-to-use implementation: Flow (MutableSharedFlow as implementation)
We never use standard callbacks anymore in our code, because they are the main source of memory leaks, do not work well with other async code (flow, suspend functions), do not support multiple subscribers (and if one day we need to support, it requires rewrite implementation completely)
If you use Flow (or even RxJava or other reactive framework), I would suggest to do the same
It’s also true for cases like button’s onClick, where it’s important to avoid memory leaks (when observer lives longer than button) and where having an option to combine with other sources of events is very useful