-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Changes from all commits
583c2ed
f2ddd6a
c26f2e2
46ea1a0
6b7cfe2
e800920
db454db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package rx.android.events; | ||
|
||
import android.view.View; | ||
import android.widget.Adapter; | ||
import android.widget.AdapterView; | ||
|
||
public class OnItemClickEvent { | ||
public final AdapterView<?> parent; | ||
public final View view; | ||
public final int position; | ||
public final long id; | ||
|
||
public OnItemClickEvent(AdapterView<?> parent, View view, int position, long id) { | ||
this.parent = parent; | ||
this.view = view; | ||
this.position = position; | ||
this.id = id; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/** | ||
* Copyright 2014 Netflix, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package rx.android.operators; | ||
|
||
import android.view.View; | ||
import android.widget.AbsListView; | ||
import android.widget.AdapterView; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.WeakHashMap; | ||
|
||
import rx.Observable; | ||
import rx.Subscriber; | ||
import rx.Subscription; | ||
import rx.android.events.OnItemClickEvent; | ||
import rx.android.observables.Assertions; | ||
import rx.android.subscriptions.AndroidSubscriptions; | ||
import rx.functions.Action0; | ||
|
||
public class OperatorAdapterViewOnItemClick implements Observable.OnSubscribe<OnItemClickEvent> { | ||
|
||
private final AdapterView<?> adapterView; | ||
|
||
public OperatorAdapterViewOnItemClick(final AdapterView<?> adapterView) { | ||
this.adapterView = adapterView; | ||
} | ||
|
||
@Override | ||
public void call(final Subscriber<? super OnItemClickEvent> observer) { | ||
Assertions.assertUiThread(); | ||
final CompositeOnClickListener composite = CachedListeners.getFromViewOrCreate(adapterView); | ||
|
||
final AbsListView.OnItemClickListener listener = new AdapterView.OnItemClickListener() { | ||
@Override | ||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { | ||
observer.onNext(new OnItemClickEvent(parent, view, position, id)); | ||
} | ||
}; | ||
|
||
final Subscription subscription = AndroidSubscriptions.unsubscribeInUiThread(new Action0() { | ||
@Override | ||
public void call() { | ||
composite.removeOnClickListener(listener); | ||
} | ||
}); | ||
|
||
composite.addOnClickListener(listener); | ||
observer.add(subscription); | ||
} | ||
|
||
private static class CompositeOnClickListener implements AbsListView.OnItemClickListener { | ||
private final List<AbsListView.OnItemClickListener> listeners = new ArrayList<AbsListView.OnItemClickListener>(); | ||
|
||
public boolean addOnClickListener(final AbsListView.OnItemClickListener listener) { | ||
return listeners.add(listener); | ||
} | ||
|
||
public boolean removeOnClickListener(final AbsListView.OnItemClickListener listener) { | ||
return listeners.remove(listener); | ||
} | ||
|
||
@Override | ||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { | ||
for (AdapterView.OnItemClickListener listener : listeners) { | ||
listener.onItemClick(parent, view, position, id); | ||
} | ||
} | ||
} | ||
|
||
private static class CachedListeners { | ||
private static final Map<AdapterView<?>, CompositeOnClickListener> sCachedListeners = new WeakHashMap<AdapterView<?>, CompositeOnClickListener>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
The only working solution I found is to store There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding the question why it have to be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it could be a hot There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From a naive standpoint, I would expect it to work like this:
but I might be missing something There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 By the way, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@mironov-nsk Agreed. But I think we should implement this in separate PR. |
||
|
||
public static CompositeOnClickListener getFromViewOrCreate(final AdapterView<?> view) { | ||
final CompositeOnClickListener cached = sCachedListeners.get(view); | ||
|
||
if (cached != null) { | ||
return cached; | ||
} | ||
|
||
final CompositeOnClickListener listener = new CompositeOnClickListener(); | ||
|
||
sCachedListeners.put(view, listener); | ||
view.setOnItemClickListener(listener); | ||
|
||
return listener; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this? You're asserting that you're subscribing from the main UI thread already. AFAIK subscribeOn and observeOn ensure that the subscription is unsubscribed from on the same thread? So you either are already on that thread (as asserted above) or observeOn does that for you if you observeOn(mainThread())
Keep in mind that using the HandlerThreadScheduler means another roundtrip through the message loop, so this will unnecessarily delay unsubscription unless I'm missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original
ViewObservable
operators don't have this check. I'm not sure why it was added after.There is an
Observable.unsubscribeOn(Scheduler)
operator.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, didn't know
unsubscribeOn
, thanks! What I was referring to is this bit:https://github.com/ReactiveX/RxJava/blob/1.x/src/main/java/rx/internal/operators/OperatorObserveOn.java#L228