Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add abstract layer for displaying elements #3

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

gkresic
Copy link

@gkresic gkresic commented Mar 8, 2021

Instead of depending on AcceptsOneWidget and its AcceptsOneWidget.setWidget(IsWidget) mechanism, introduce abstract ActivityDisplay<V> which can be implemented to provide displaying methods for any widgetset.

Also, provide two simple ActivityDisplay implementations (WidgetActivityDisplay and ElementalActivityDisplay) for most common scenarios.

Specialized test cases are not needed, because ActivityManagerTest.MyDisplay is updated to directly implement ActivityDisplay<IsWidget> so this abstraction is already covered by tests.

*/
public class WidgetActivityManager extends ActivityManager<IsWidget> {

private AcceptsOneWidget widgetDisplay;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's never assigned, maybe it doesn't really matter if passing the same display should be a no-op or not (ActivityManager doesn't really care, all it cares about apparently is whether we go from null to non-null, or from non-null to null), but it's a breaking change.

This should be assigned in setDisplay, and tested for non-regression wrt c.g.g.activity.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's never assigned, maybe it doesn't really matter if passing the same display should be a no-op or not (ActivityManager doesn't really care, all it cares about apparently is whether we go from null to non-null, or from non-null to null), but it's a breaking change.

Indeed, I didn't see that. I saw it was comparing it (instance compare), so I took the safe route and decided to provide same wrapper for same delegate. If my 2 cents are worth anything, I wouldn't care about instancing new wrapper for each call since it results in much more readable code.

This should be assigned in setDisplay, and tested for non-regression wrt c.g.g.activity.

Yes, that was intention, but I forgot and since ActivityManager really doesn't care (like you pointed), it didn't matter in tests.

I'll initialize it, but will leave a note for possible simplification (with performance penalty, though).


private AcceptsOneWidget widgetDisplay;

private ActivityDisplay<IsWidget> wrapperDisplay;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't it be a local variable?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, no, at least if we want to return same wrapper for each display. Note that it's initialized inside if, only if display is different from previous one. Otherwise, its left as an existing wrapper used in prior call.

*/
public interface WidgetActivityMapper extends ActivityMapper<IsWidget> {

WidgetActivity getActivity(Place place);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have an @Override

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.

public interface WidgetActivity extends Activity<IsWidget> {

default void start(ActivityDisplay<IsWidget> panel, EventBus eventBus) {
start((AcceptsOneWidget) panel::show, eventBus);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because that's what ActivityManager will call, it means the start(AcceptsOneWidget,EventBus) will receive a different AcceptsOnePanel each time it's started, even if it's actually the same underlying AcceptsOnePanel we passed to the ActivityManager. That panel will have been wrapped twice here: once to convert it to an ActivityDisplay in WidgetActivityManager, and then to converted it back to an AcceptsOneWidget here.
Maybe using an actual wrapper class and testing it here with instanceof would be better?

class AcceptsOneWidgetActivityDisplayWrapper {
  final AcceptsOneWidget panel;

  AcceptsOneWidgetActivityDisplayWrapper(AcceptsOneWidget panel) {
    this.panel = panel;
  }
}
AcceptsOneWidget panel;
if (display instanceof AcceptsOneWidgetActivityDisplayWrapper) {
  panel = ((AcceptsOneWidgetActivityDisplayWrapper) panel).panel;
} else {
  panel = (AcceptsOneWidget) display::show
}
start(panel, eventBus);

Unless we a) those multiple wrappers aren't a problem in practice (in terms of performance and/or code size) and b) don't care about the breaking change (any activity testing either the type of the panel or whether it's the same as in the previous start will break when migrated to this lib)

Copy link
Author

@gkresic gkresic Mar 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I said, I would choose simplicity over microoptimization any time, but like you pointed out, this may actually break activities if they compare panels (didn't occur to me they may), so I agree this is safer. Will do.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like activities never had an opportunity to compare panels they got, since ActivityManager wraps every display/panel with new ProtectedDisplay every time Activity.start is invoked (see ActivityManager.tryStart).

So, (double) wrapping seems inevitable.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And on the other hand, that simplifies the code a lot, as setDisplay(AcceptsOnWidget panel) can then just call setDisplay(panel == null ? null : panel::setWidget) (i.e. you can remove the widgetDisplay and wrapperDisplay)

super(mapper, eventBus);
}

public void setDisplay(AcceptsOneWidget display) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this extend or wrap?

  • by extending, this allows any ActivityDisplay<IsWidget>, but breaks setDisplay(null) that now requires a explicit cast to choose an overload
  • by wrapping, it cannot be used interchangeably with an ActivityManager<IsWidget> (similar to the CompatEventBus in gwt-event-compat), but ensures backwards compatibility

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like you pointed out, either one is bad. Javadoc could alleviate issue with required cast of nulls, but it's far from optimal. What is your suggestion?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would wrap, because I don't see a need for casting to ActivityManager<IsWidget>. And I would have a constructor that takes the ActivityManager<IsWidget> to be wrapped, and another constructor that would instantiate one, for ease of use. If one needs both an ActivityManager<IsWidget> and a WidgetActivityManager, they could then create the former and wrap it in the latter (similar to CompatEventBus)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapped.

I exposed all public methods from wrapped ActivityManager<IsWidget> together with wrapped object itself (via (getDelegateManager) and explained relationship to ActivityManager<IsWidget> in docs.

gkresic added 4 commits March 9, 2021 18:36
Since ActivityManager:

a) cares only whether display is null or non-null

b) wraps configured display with ProtectedDisplay before it passes it to Activity

it's safe to wrap AcceptsOneWidget in new instance of ActivityDisplay on every call
to setDisplay.
Don't extend, wrap. Prevents ambiguity when calling setDisplay(null).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants