Unsubscribing from RxJava2/RxAndroid PublishSubject - rx-java2

I'm trying to replace EventBus with RxAndroid.
I want pageable fragments to subscribe/unsubscribe to an event source, these fragments get created and discarded relatively quickly, depending on how fast the user slides to a new page.
In EventBus I was able to add an decorated callback method (ie #Subscribe(threadMode = ThreadMode.MAIN)) and register/unregister in the onStart/onStop methods of the fragment.
With RxJava2 I now create a PublishSubject object in a class
public static PublishSubject<List<Long>> m_psUpdatedDays = PublishSubject.create();
public static void publishUpdatedDays(List<Long> lDay) {
m_psUpdatedDays.onNext(lDay);
}
and subscribe to this publisher in another class by calling the following in the Fragment's onStart method:
m_psUpdatedDays.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<List<Long>>() {
#Override public void onSubscribe(Disposable d) {}
#Override public void onNext(List<Long> longs) {
...
Update Fragment UI here
...
}
#Override public void onError(Throwable e) {}
#Override public void onComplete() {}
});
My question is how can I unsubscribe this new Observer when the Fragment's onStop method is called by the system?
Do I need to store the Disposable object which I get in the onSubscribe and then call .dispose() on it in the onStop method?

You can make use of a CompositeDisposable object, which can keep a list of disposables and all of them can be disposed together.
Create a CompositeDisposable instance in the base fragment level, keep on adding your disposables into it.
public abstract class BaseFragment extends Fragment {
protected CompositeDisposable mCompositeDisposable = new CompositeDisposable();
#Override
public void onPause() {
super.onPause();
mCompositeDisposable.clear();
//clear will clear all, but can accept new disposable.
// You can call it on `onPause` or `orDestroyView` events.
}
#Override
public void onDestroy() {
super.onDestroy();
mCompositeDisposable.dispose();
//dispose will clear all and set isDisposed = true, so it will not accept any new disposable
}
}
In your fragments, subscribe to the Observable using the subscribeWith method, which gives you a disposable instantly and this disposable you can dispose later in the onPause or onDestroy events (wherever you want)
public class MyFragment extends BaseFragment {
#Override
public void onStart() {
super.onStart();
Disposable disposable = m_psUpdatedDays.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<List<Long>>() { // Use `subscribeWith` instead of `subscribe`, which will give you back the disposable , which can be disposed later
#Override
public void onNext(List<Long> longs) {
// Update Fragment UI here
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
mCompositeDisposable.add(disposable); // add the disposable to the disposable list
}
}

Related

Dynamic Merge of Infinite Reactor streams

Usecase:
There is a module which Listens for events in synchronous mode. In the same module using the EmitterProccessor, the event is converted to Flux and made as infinite stream of events. Now there is a upstream module which can subscribes for these event streams. The problem here is how can I dynamically merge these streams to one and then subscribe in a single stream. A simple example is, let us say there are N number of sensors, we can dynamically register these sensors and start listening for measurements as stream of data in single stream after merging them into one stream. Here is the code sample written to mock this behavior.
Create callback and start listening for events
public interface CallBack {
void callBack(int name);
void done();
}
#Slf4j
#RequiredArgsConstructor
public class CallBackService {
private CallBack callBack;
private final Function<Integer, Integer> func;
public void register(CallBack intf) {
this.callBack = intf;
}
public void startServer() {
log.info("Callback started..");
IntStream.range(0, 10).forEach(i -> {
callBack.callBack(func.apply(i));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
log.info("Callback finished..");
callBack.done();
}
}
Convert the events to streams using event proccessor
#Slf4j
public class EmitterService implements CallBack {
private EmitterProcessor<Integer> emitterProcessor;
public EmitterService(){
emitterProcessor = EmitterProcessor.create();
}
public EmitterProcessor<Integer> getEmmitor() {
return emitterProcessor;
}
#Override
public void callBack(int name) {
log.info("callbakc {} invoked", name);
//fluxSink.next(name);
emitterProcessor.onNext(name);
}
public void done() {
//fluxSink.complete();
emitterProcessor.onComplete();
}
}
public class WrapperService {
EmitterService service1;
ExecutorService service2;
public Flux<Integer> startService(Function<Integer, Integer> func) {
CallBackService service = new CallBackService(func);
service1 = new EmitterService();
service.register(service1);
service2 = Executors.newSingleThreadExecutor();
service2.submit(service::startServer);
return service1.getEmmitor();
}
public void shutDown() {
service1.getEmmitor().onComplete();
service2.shutdown();
}
}
Subscribe for the events
#Slf4j
public class MainService {
public static void main(String[] args) throws InterruptedException {
TopicProcessor<Integer> stealer = TopicProcessor.<Integer>builder().share(true).build();
CountDownLatch latch = new CountDownLatch(20);
WrapperService n1 =new WrapperService();
WrapperService n2 =new WrapperService();
// n1.startService(i->i).mergeWith(n2.startService(i->i*2)).subscribe(stealer);
n1.startService(i->i).subscribe(stealer);
n2.startService(i->i*2).subscribe(stealer);
stealer.subscribeOn(Schedulers.boundedElastic())
.subscribe(x->{
log.info("Stole=>{}", x);
latch.countDown();
log.info("Latch count=>{}", latch.getCount());
});
latch.await();
n1.shutDown();
n2.shutDown();
stealer.shutdown();
}
}
Tried to use TopicProccessor with no success. In the above code subscription happens for first source, for second source there is no subscription. however if use n1.startService(i->i).mergeWith(n2.startService(i->i*2)).subscribe(stealer); subscription works, but there is no dynamic behavior in this case. Need to change subscriber every time.

Setting a WorkbenchWindowControlContribution as a SelectionProvider

I added a control in the toolbar which extends WorkbenchWindowControlContribution and implements ISelectionProvider. This control contains a Combo and when I change the selection I want to notify another view. Because it's not a ViewPart I can't call getSite().setSelectionProvider directly. I tried to set it like this:
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart().getSite().setSelectionProvider(MyControl.this);
});
If I add this to the protected Control createControl(Composite parent) method the getActivePart would always be null. I also tried registering it when the Combo's value changes for the first time, but that way my other View wasn't notified about the change event.
Here is the ISelectionProvider methods:
#Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
listeners.add(listener);
}
#Override
public ISelection getSelection() {
return new StructuredSelection(comboBox.getText());
}
#Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
listeners.remove(listener);
}
#Override
public void setSelection(ISelection selection) {
for (ISelectionChangedListener listener : listeners) {
listener.selectionChanged(new SelectionChangedEvent(this, selection));
}
}
The View that should be getting notified implements ISelectionListener. I register it in the public void createPartControl(Composite parent) with getSite().getPage().addSelectionListener(this);
And here is the override of the SelectionChanged too:
#Override
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
if (part instanceof MyControl) {
String selectedProtocol = ((StructuredSelection) selection).getFirstElement().toString();
}
}
Can it be done?
Thanks!

Activity handlers don't get removed

I'm trying to get up to speed on using GWT Activities and Places. I'm testing with some source code originally found on this good blog post.
I'm finding the Handlers that get added during bind() never seem to removed. My little understanding of the Activity javadoc had me thinking they should get automagically removed by the time the Activity's onStop() method is invoked.
All event handlers it registered will have been removed before this
method is called.
But each time I click a button the corresponding handler is called n+1 times.
What am I missing? Please let me know if there is more info I can provide.
Here's a relevant snippet from the code:
public class ContactsActivity extends AbstractActivity {
private List<ContactDetails> contactDetails;
private final ContactsServiceAsync rpcService;
private final EventBus eventBus;
private final IContactsViewDisplay display;
private PlaceController placeController;
public interface IContactsViewDisplay {
HasClickHandlers getAddButton();
HasClickHandlers getDeleteButton();
HasClickHandlers getList();
void setData(List<String> data);
int getClickedRow(ClickEvent event);
List<Integer> getSelectedRows();
Widget asWidget();
}
public ContactsActivity(ClientFactory factory) {
GWT.log("ContactActivity: constructor");
this.rpcService = factory.getContactServiceRPC();
this.eventBus = factory.getEventBus();
this.display = factory.getContactsView();
this.placeController = factory.getPlaceController();
}
#Override
public void start(AcceptsOneWidget container, EventBus eventBus) {
GWT.log("ContactActivity: start()");
bind();
container.setWidget(display.asWidget());
fetchContactDetails();
}
public void bind() {
GWT.log("ContactActivity: bind()");
display.getAddButton().addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
GWT.log("Add button clicked");
ContactsActivity.this.placeController.goTo(new NewContactPlace(""));
}
});
display.getDeleteButton().addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
GWT.log("ContactActivity: Delete button clicked");
deleteSelectedContacts();
}
});
display.getList().addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
GWT.log("ContactActivity: List clicked");
int selectedRow = display.getClickedRow(event);
if (selectedRow >= 0) {
String id = contactDetails.get(selectedRow).getId();
ContactsActivity.this.placeController.goTo(new EditContactPlace(id));
}
}
});
}
Events registered via. the EventBus passed to AbstractActivity#start() will be unregistered by the time onStop() is called. The event handlers registered in the above bind() method, however, are not registered via the EventBus and are not visible to the abstract base class. You need to unregister them yourself:
public class ContactsActivity extends AbstractActivity {
private List<HandlerRegistration> registrations = new ArrayList();
private void bind() {
registrations.add(display.getAddButton().
addClickHandler(new ClickHandler() { ... }));
registrations.add(display.getDeleteButton().
addClickHandler(new ClickHandler() { ... }));
registrations.add(display.getList().
addClickHandler(new ClickHandler() { ... }));
}
#Override
public void onStop() {
for (HandlerRegistration registration : registrations) {
registration.removeHandler();
}
registrations.clear();
}
}
I found it best to handle registration in the view - make it responsible for only keeping one click hander active for each button.
Instead of:
class View {
Button commitButton;
public HasClickHandlers getCommit () {return commitButton;}
}
..and link to this in the Activity:
view.getCommit.addClickHandler(new Clickhandler()...
Do this in the View:
class View {
private Button commitButton;
private HandlerRegistration commitRegistration = null;
public void setCommitHandler (ClickHandler c) {
commitRegistraion != null ? commitRegistration.removeRegistration ();
commitRegistration = commitButton.addClickHandler (c);
}
}
And the Activity:
view.setCommitHandler (new ClickHandler () ...
Hope that helps.

GWT RPC mechanism how to use non void return type

I have a scenario wherein I need to specify a return type to the Synchrnous function, the code is as follows :
#RemoteServiceRelativePath("show_box")
public interface ShowBoxCommandService extends RemoteService{
public ArrayList<String> showBox();
}
The implementation of the method on the server is :
public ArrayList<String> showBox() {
ArrayList<String> box = new ArrayList<String>();
Iterator<Box> boxes = BoxRegistry.getInstance().getBoxes();
while (boxes.hasNext()) {
box.add(boxes.next().toString());
}
return box;
}
I am trying to define the callback variable in the following format at the client side in order to call the method
AsyncCallback<Void> callback = new AsyncCallback<Void>() {
public void onFailure(Throwable caught) {
// TODO: Do something with errors.
// console was not started properly
}
#Override
public void onSuccess(Void result) {
// TODO Auto-generated method stub
// dialog saying that the console is started succesfully
}
};
update with the aync interface code :
public interface ShowBoxCommandServiceAsync {
void showBox(AsyncCallback<ArrayList<String>> callback);
}
But this is causing the definition of the method in the Async method to change.
Any ideas or clues will be helpful.
Thanks,
Bhavya
P.S. Apologies if this is a repetition
The callback should be:
AsyncCallback<ArrayList<String>> callback = new AsyncCallback<ArrayList<String>>() {
public void onFailure(Throwable caught) {
// TODO: Do something with errors.
// console was not started properly
}
#Override
public void onSuccess(ArrayList<String> result) {
// TODO Auto-generated method stub
// dialog saying that the console is started succesfully
}
};
If you don't need to utilize the result then you can ignore it, but if that is the case, you should probably question your design and why you would need the method to return an ArrayList<String> in the first place.
If the service interface looks like this:
public interface ShowBoxCommandService extends RemoteService {
public ArrayList<String> showBox();
}
then you must have an associated async interface:
public interface ShowBoxCommandServiceAsync {
public void showBox(AsyncCallback<ArrayList<String>> callback);
}
Which means, that the type of the callback that you should pass to showBox is AsyncCallback<ArrayList<String>>.
new AsyncCallback<ArrayList<String>>() {
#Override
public void onSuccess(ArrayList<String> list) {
// ...
}
#Override
public void onFailure(Throwable caught) {
// ...
}
}
Your callback should not be Void. If your synchronous method returns a List of Strings, the async callback method should receive the List. You'll have to use the ArrayList, because the class needs to implement the Serializable interface.
AsyncCallback<ArrayList<String>> callback = new AsyncCallback<ArrayList<String>>() {
public void onFailure(Throwable caught) {
// TODO: Do something with errors.
// console was not started properly
}
#Override
public void onSuccess(ArrayList<String> result) {
// TODO Auto-generated method stub
// dialog saying that the console is started succesfully
}
};
Huh? Your method returns an ArrayList and you are declaring void in your call?
Change <Void> to <ArrayList<String>>

Register KeyDownHandler on GWT VerticalPanel

I have a gwt VerticalPanel class that i need to handel KeyDown events for it.
the method i used to implement keyboard handler in my class is:
i add :
this.sinkEvents(Event.ONKEYDOWN);
to constructor
then i override method onBrowserEvent() to handle key down event.
#Override
public void onBrowserEvent(Event event) {
// TODO Auto-generated method stub
super.onBrowserEvent(event);
int type = DOM.eventGetType(event);
switch (type) {
case Event.ONKEYDOWN:
//call method to handle this keydown event
onKeyDownEvent(event);
break;
default:
return;
}
}
however this method doesn’t work for this VerticalPanel class.no KeyDown Event is fired when i press a key!
there are specific gwt widgets that support KeyDownHandler like Button etc..VerticalPanel is not one of them..so we need a work around to register a KeyDownHandler on a class extending VerticalPanel.
can you suggest an idea or hint?
thanks
You could create a Composite that wrappes a FocusPanel and a VerticalPanel. This way you can catch all key events provided the FocusPanel is focused. Simply delegate the needed methods to the panels:
public void onModuleLoad() {
ExtendedVerticalPanel panel = new ExtendedVerticalPanel();
panel.add(new Label("some content"));
panel.addKeyDownHandler(new KeyDownHandler() {
#Override
public void onKeyDown(KeyDownEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
Window.alert("enter hit");
}
}
});
RootPanel.get().add(panel);
}
private class ExtendedVerticalPanel extends Composite implements HasWidgets, HasAllKeyHandlers {
private VerticalPanel fVerticalPanel;
private FocusPanel fFocusPanel;
public ExtendedVerticalPanel() {
fVerticalPanel = new VerticalPanel();
fFocusPanel = new FocusPanel();
fFocusPanel.setWidget(fVerticalPanel);
initWidget(fFocusPanel);
}
#Override
public void add(Widget w) {
fVerticalPanel.add(w);
}
#Override
public void clear() {
fVerticalPanel.clear();
}
#Override
public Iterator<Widget> iterator() {
return fVerticalPanel.iterator();
}
#Override
public boolean remove(Widget w) {
return fVerticalPanel.remove(w);
}
#Override
public HandlerRegistration addKeyUpHandler(KeyUpHandler handler) {
return fFocusPanel.addKeyUpHandler(handler);
}
#Override
public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
return fFocusPanel.addKeyDownHandler(handler);
}
#Override
public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
return fFocusPanel.addKeyPressHandler(handler);
}
}
UPDATE
Your question on how to prevent the browser from scrolling when the arrow keys are pressed. Here a small example that works for me:
public void onModuleLoad() {
ExtendedVerticalPanel panel = new ExtendedVerticalPanel();
// make panel reeeeaally big
panel.setHeight("3000px");
panel.add(new TextBox());
panel.addKeyDownHandler(new KeyDownHandler() {
#Override
public void onKeyDown(KeyDownEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_DOWN) {
Window.alert("down hit");
event.preventDefault();
}
}
});
RootPanel.get().add(panel);
}
Add the handlers you need and call preventDefault() on the events the browser must not take care of.