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 OperatorAdapterViewOnItemClick to observe OnItemClick events in AdapterViews. Resolve #19. #20

Merged
merged 7 commits into from
Oct 15, 2014
Merged

Add OperatorAdapterViewOnItemClick to observe OnItemClick events in AdapterViews. Resolve #19. #20

merged 7 commits into from
Oct 15, 2014

Conversation

kboyarshinov
Copy link
Contributor

  • new OperatorAdapterViewOnItemClick to handle onItemClick events from AdapterViews;
  • separate OnItemClickEvent class to observe it;
  • corresponding itemClicks method at ViewObservable;
  • tests for ListView and GridView, also with multiple subscriptions.

@nsk-mironov
Copy link
Contributor

LGTM, but I think we should make an agreement on ViewObservable conventions. At the moment all ViewObservable methods are inconsistent with each other and emit different type of things:

  • ViewObservable.clicks emits a view itself
  • ViewObservable.input emits a boolean value
  • ViewObservable.itemClicks emits a custom event type

I really like the idea of OnItemClickEvent and I think we should follow the same convention for other methods:

public static <T extends View> Observable<OnClickEvent<T>> clicks(final T view, final boolean emitInitialValue);
public static <T extends TextView> Observable<OnTextChangeEvent<T>> text(final T view, final boolean emitInitialValue);
public static <T extends CompoundButton> Observable<OnCheckedChangeEvent<T>> input(final T view, final boolean emitInitialValue);

public class OnClickEvent<T extends View> {
    public final T view;
}

public class OnTextChangeEvent<T extends TextView> {
    public final T view;
    public final CharSequence text;
}

public class OnCheckedChangeEvent<T extends CompoundButton> {
    public final T view;
    public final boolean value;
}

I know, it's a breaking change, but RxAndroid is still not even 1.0, so breaking changes should be expected. Also, it will be really easy to solve #7 using this approach.

Any thoughts?

/cc @mttkay

@mttkay
Copy link
Collaborator

mttkay commented Oct 6, 2014

@mironov-nsk that sounds sensible. I have to say we don't use RxJava on the view level very much, so I'll let you guys drive the discussion, but I think it's a good idea what you propose. I'm fine with breaking changes on the road to 1.0; the whole idea behind breaking out the contrib module from core was to have independent roadmaps. There's no urgency to rush any of this from my point of view. RxJava had many breaking changes over the last year and a half on its way to 1.0. That's only expected.

@kboyarshinov
Copy link
Contributor Author

@mironov-nsk events convention looks better than view and primitive types. Agree with that.
Considering this OnItemClickEvent would look like

public final class OnItemClickEvent <T extends AdapterView<?>> {
    public final T parent;
    public final View view;
    public final int position;
    public final long id;

    public OnItemClickEvent(T parent, View view, int position, long id) {
        this.parent = parent;
        this.view = view;
        this.position = position;
        this.id = id;
    }
}

Also we should do something with AutoCompleteTextView suggestions list mentioned in #19. Corresponding operator would be the same as for AdapterView cause AutoCompleteTextView is just wrapping DropDownListView. But, not as testable. Adding just operator without tests is bad idea.

@nsk-mironov
Copy link
Contributor

@naghtarr I'm also wondering if events should be generics or not. Observable<OnItemClickEvent<ListView>> and Observable<OnTextChangeEvent<TextView>> look a bit verbose to me, so it's probably better to keep a reference to a base type (as you did in your pull request) and make it Observable<OnItemClickEvent> and Observable<OnTextChangeEvent>.

@kboyarshinov
Copy link
Contributor Author

@mironov-nsk Observable<OnItemClickEvent> and Observable<OnTextChangeEvent> are better. Also it allows to use these events in more complex views like AutoCompleteTextView. Generics will be a restriction.

@kboyarshinov
Copy link
Contributor Author

Cleaned up generics as discussed. Moved to package rx.android.operators. Made OnItemClickEvent class non-final. Changes are consisted with #22.

}

private static class CachedListeners {
private static final Map<AdapterView<?>, CompositeOnClickListener> sCachedListeners = new WeakHashMap<AdapterView<?>, CompositeOnClickListener>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just wondering, are we just being defensive here? As far as I understand, unsubscribing removes a listener already, so why use weak references?

Also, I don't think this should be static, as this means we're sharing mutable state with multiple instances of this class?

Copy link
Contributor

Choose a reason for hiding this comment

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

@mttkay We're not defensive here, this is the only way I found to make multiple subscriptions work. There are two problems here:

  • Methods like setOnClickListener and setOnItemClickListener allow to set only one listener, making multiple subscriptions hard to implement. This is the reason we need classes like CompositeOnClickListener.
  • There is no way to get a OnXXXListener attached to the View (since there are no getOnXXXListener methods), so we need to save listeners somewhere else. View.setTag(Object tag) is not an option here since it's heavily used by the end developers. View.setTag(int key, Object key) could be a solution, but it requires a key to be declared in the resources, making it impossible to ship the library as a jar.

The only working solution I found is to store CompositeXXXListeners in the global WeekHasMap (it have to be static). Any other ideas how to solve this problem are welcome.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't understand. Are you expecting that this operator is called multiple times for different listeners, i.e. will call be executed more than once on the same observable?

Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding the question why it have to be a WeakHashMap. Well, it could be just a usual HashMap, but in this case user have to unsubscribe from the Observable in order to prevent memory leaks, making the usage of ViewObservables a bit more complicated.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand. Are you expecting that this operator is called multiple times for different listeners, i.e. will call be executed more than once on the same observable?

Yes, call will be executed more than once if user subscribes to the Observable multiple times.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Making it a hot observable? Why not deal with this using a built in operator like publish? Then you can have multiple subscribers for the same OnClick event

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, it could be a hot Observable, but we still need somehow to attach the Observable to the View

Copy link
Collaborator

Choose a reason for hiding this comment

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

From a naive standpoint, I would expect it to work like this:

ConnectableObservable source = sourceSequence.lift(new OperatorAdapterViewOnItemClick(listView)).publish();
source.connect();
source.subscribe(o1);
source.subscribe(o2);

but I might be missing something

Copy link
Contributor

Choose a reason for hiding this comment

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

The point here is to make all this complicity transparent to the end developers. I don't think it's a good idea to force them to use ConnectableObservable instead of providing the same functionality out of the box.

By the way, OperatorAdapterViewOnItemClick is not a real Operator, since it implements OnSubscribe interface, but not the Operator one. Probably we should follow the conventions used in RxJava and name it something like OnSubscribeViewOnItemClick (and rename all other "operators" as well).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably we should follow the conventions used in RxJava and name it something like OnSubscribeViewOnItemClick (and rename all other "operators" as well).

@mironov-nsk Agreed. But I think we should implement this in separate PR.

@mttkay
Copy link
Collaborator

mttkay commented Oct 13, 2014

Cool. Is this ready to land?

@kboyarshinov
Copy link
Contributor Author

@mttkay Yes.

@mttkay
Copy link
Collaborator

mttkay commented Oct 15, 2014

Sorry, landing another PR made this unmergeable. Will merge after this is resolved!

@kboyarshinov
Copy link
Contributor Author

@mttkay, Merged 0.x changes to this branch.

@mttkay
Copy link
Collaborator

mttkay commented Oct 15, 2014

👍

mttkay added a commit that referenced this pull request Oct 15, 2014
Add OperatorAdapterViewOnItemClick to observe OnItemClick events in AdapterViews. Resolve #19.
@mttkay mttkay merged commit ddbd8d7 into ReactiveX:0.x Oct 15, 2014
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.

3 participants