I'm trying to figure out a better way to achieve something like repository pattern in RxJava in Android.
Here's what I have so far: (took some code from here)
public Subscription getData(Observer<Data> observer, boolean refresh) {
Subscription sub = null;
Data cached = getCachedData();
if(cached != null) {
observer.onNext(cached);
if(refresh) {
sub = requestNetwork().subscribe(observer);
} else {
observer.onCompleted();
}
} else {
sub = requestNetwork().subscribe(observer);
}
return sub;
}
Basically it check if there's cached data stored, if not it'll make a network request. It also have refresh boolean parameter force it always make a network request.
The problem (or not) is, the caller of this function needs to call it will receive Subscription instead of Observable, which I can't chain anymore.
Is there a way to make the function return Observable but still have the repository pattern?
Thanks to akarnokd pointing me out to this article by Dan Lew.
My final code:
public Observable<Data> getData(boolean refresh) {
Observable<Data> obs = Observable.concat(getCache(), requestNetwork());
if(!refresh) {
obs = obs.first(data -> data != null);
}
return obs;
}
Related
Context
I'm building a Flutter Plugin above the DJK SDK. For that, I have to implement the communication with the aircraft on the native side, and I'm doing it with Java. I'm also doing it only for Android.
One of the methods of the API is boolean connectToAircraft(), which must return if the connection with the aircraft succeeded.
Expected/current behavior
After I call connectToAircraft() - which invokes the DJISDKManager.getInstance().startConnectionToProduct() method, I expected to be able to use anything related to aircraft immediately, but this doesn't happen. I have to wait a few seconds before I can retrieve data from the aircraft.
Some code
public class UavApi implements IUavApi, DJISDKManager.SDKManagerCallback {
...
private final CountDownLatch onConnectToUavFinishedSignal = new CountDownLatch(1);
...
public boolean connectToUav() throws InterruptedException {
Logger.v("connectToUav()");
DJISDKManager.getInstance().startConnectionToProduct();
synchronized (onConnectToUavFinishedSignal) {
onConnectToUavFinishedSignal.await();
}
return DJISDKManager.getInstance().getProduct() instanceof Aircraft;
}
...
#Override
public void onProductConnect(#Nullable final BaseProduct baseProduct) {
Logger.v(MessageFormat.format("onProductConnect(product: {0})", baseProduct));
if (baseProduct != null) {
handleProductConnected(baseProduct);
}
}
#Override
public void onProductChanged(#Nullable final BaseProduct baseProduct) {
Logger.v(MessageFormat.format("onProductChanged(product: {0})", baseProduct));
if (baseProduct != null) {
handleProductConnected(baseProduct);
}
}
...
private void handleProductConnected(#NonNull final BaseProduct baseProduct) {
Logger.d(MessageFormat.format("Is null? {0}", baseProduct == null ? "Yes" : "No"));
Logger.d(MessageFormat.format("Type: {0}", baseProduct.getClass().getSimpleName()));
onConnectToUavFinishedSignal.countDown();
}
...
}
Problem
The code above is what I tried to do, but it's not working and guess it's because I'm misunderstanding the use of the onProductChange() and onProductConnect() methods.
The DJISDKManager.getInstance().getProduct() is always returning null.
OBS: It's always returning null immediately after the onConnectToUavFinishedSignal.await() call finishes. After a few seconds, I get a valid instance of the aircraft.
Something I've also noticed is that sometimes the onProductChange() is called with some value that the log outputs as Unknwoun and None. What are those and how can I test for them? Like if (baseProduct == ???) doSomething()
Environment
Android 9
MSDK 4.13.1
Phantom 4 Pro
Difference
According to the SDK Docs onProductChanged is primarily used to detect when the connection status changes from only remote controller connected to a full connection between the aircraft and the SDK running on your device.
Keep in mind that when the aircraft is disconnected, this method will be called with an instance of an aircraft, but this instance will come with property isConnected as false. If you print the aircraft object to the console you will notice that if isConnected is true, it will print the aircraft name, otherwise, it will print "None".
As long for the onProductConnect, it will be called always after DJISDKManager.getInstance().registerApp() succeeded or after you manually connect to the aircraft with success using DJISDKManager.getInstance().startConnectionToProduct(). In my tests, even though the app registration succeeds, the method will return false, so you might need to check if the SDKManagerCallback::onRegister results in DJISDKError.REGISTRATION_SUCCESS.
Solution
You need to listen to component change events. Unfortunately just because the product is connected it does not mean that the individual components, such as the flight controller, camera etc are connected. You will need to implement onComponentChange and add a listener to detect when a component is connected. These don't always connect in the same order and may start to connect before or after the product is connected.
#Override
public void onComponentChange(
BaseProduct.ComponentKey componentKey,
BaseComponent oldBaseComponent,
BaseComponent newBaseComponent
) {
newBaseComponent.setComponentListener(isConnected -> {
// check if component connected and access data
if (isConnected) {
if(componentKey == ComponentKey.FLIGHT_CONTROLLER) {
// DJISDKManager.getInstance().getProduct() should no longer be null
DJISDKManager.getInstance().getProduct().getModel();
}
}
})
}
I'm trying to combine two forms insertion in one using RxJava, RxAndroid and Mosby3, but I can't find a way to make it work.
My structure:
public final class CheckinIntent {
private final CheckinCommand checkinCommand;
private final Bitmap signature;
public CheckinIntent(CheckinCommand checkinCommand, Bitmap signature) {
this.checkinCommand = checkinCommand;
this.signature = signature;
}
public CheckinCommand getCheckinCommand() {
return checkinCommand;
}
public Bitmap getSignature() {
return signature;
}
}
Where I fire my intent (MVI pattern):
final Observable<Bitmap> signatureObservable = Observable.just(BitmapFactory.decodeFile(storage.getFile("signs", booking.getBookingId()).getAbsolutePath()));
final Observable<CheckinCommand> checkinCommandObservable = Observable.just(new CheckinCommand(booking.getBookingId(), booking.getUserId(), booking.getPartnerId(), userDetailsTextView.getText().toString(), "google.com"));
final Observable<CheckinIntent> intentObservable = Observable.zip(signatureObservable, checkinCommandObservable, (image, command) -> new CheckinIntent(command, image));
return saveButtonClickObservable
.flatMap(bla -> intentObservable);
And binding it all together:
#Override
protected void bindIntents() {
Observable<CheckinViewState> checkinViewStateObservable =
intent(CheckinView::sendCheckin)
.flatMap(checkinIntent -> imageRepository.uploadImage(checkinIntent.getSignature())
.flatMap(command -> bookingRepository.doCheckin(command) <------ PROBLEM HERE, HOW CAN I ACCESS THE COMMAND FROM ABOVE ??
.subscribeOn(Schedulers.from(threadExecutor))
.map(CheckinViewState.Success::new)
.cast(CheckinViewState.class)
.startWith(new CheckinViewState.LoadingState())
.onErrorReturn(CheckinViewState.ErrorState::new))
.observeOn(postExecutionThread.getScheduler());
subscribeViewState(checkinViewStateObservable, CheckinView::render);
}
Observable<CnhImageResponse> uploadImage(Bitmap bitmap);
My problem is, my uploadImage returns an internal structure that ends of on a String, but, how can I get the returned string, add it to my command object (setting the returned URL in this object) and continue the flow (sending my command to the cloud) ?
Thanks!
Just flatMap on the observable directly within the first flatMap. In that case you have reference to both, the checkinIntent and command
#Override
protected void bindIntents() {
Observable<CheckinViewState> checkinViewStateObservable =
intent(CheckinView::sendCheckin)
.flatMap(checkinIntent -> {
return imageRepository.uploadImage(checkinIntent.getSignature()
.flatMap(imageResponse -> bookingRepository.doCheckin(command) <-- Now you have access to both, command and CnhImageResponse
})
.subscribeOn(Schedulers.from(threadExecutor))
.map(CheckinViewState.Success::new)
.cast(CheckinViewState.class)
.startWith(new CheckinViewState.LoadingState())
.onErrorReturn(CheckinViewState.ErrorState::new))
.observeOn(postExecutionThread.getScheduler());
subscribeViewState(checkinViewStateObservable, CheckinView::render);
}
Alternative solution: Pass a Pair<CheckinIntent, Command> to the Observable from bookingRepository.doCheckin(...) like this:
#Override
protected void bindIntents() {
Observable<CheckinViewState> checkinViewStateObservable =
intent(CheckinView::sendCheckin)
.flatMap(checkinIntent -> imageRepository.uploadImage(checkinIntent.getSignature()
.map(imageResponse -> Pair.create(checkinIntent, imageResponse))) // Returns a Pair<CheckinIntent, CnhImageResponse>
.flatMap(pair -> bookingRepository.doCheckin(pair.first) <-- Now you can access the pair holding both information
.subscribeOn(Schedulers.from(threadExecutor))
.map(CheckinViewState.Success::new)
.cast(CheckinViewState.class)
.startWith(new CheckinViewState.LoadingState())
.onErrorReturn(CheckinViewState.ErrorState::new))
.observeOn(postExecutionThread.getScheduler());
subscribeViewState(checkinViewStateObservable, CheckinView::render);
}
Just a few other notes:
You almost ever want to prefer switchMap() over flatMap() in MVI. switchMap unsubscribes previous subscription while flatMap doesnt. That means that if you flatMap as you did in your code snipped and if for whatever reason a new checkinIntent is fired while the old one hasn't completed yet (i.e. imageRepository.uploadImage() is still in progress) you end up having two streams that will call CheckinView::render because the first one still continue to work and emit results down through your established observable stream. switchMap() prevents this by unsubscribing the first (uncompleted) intent before starting "switchMaping" the new intent so that you only have 1 stream at the time.
The way you build your CheckinIntent should be moved to the Presenter. This is kind of too much "logic" for a "dump" View. Also Observable.just(BitmapFactory.decodeFile(...)) is running on the main thread. I recommend to use Observable.fromCallable( () -> BitmapFactory.decodeFile(...)) as the later deferres his "work" (bitmap decoding) until this observable is actually subscribed and then you can apply background Schedulers. Observable.just() is basically the same as:
Bitmap bitmap = BitmapFactory.decodeFile(...); // Here is the "hard work" already done, even if observable below is not subscribed at all.
Observable.just(bitmap);
I am attempting to write a Reactive Stream based on the following information:
We have a stream of Entity Events where each Event contains the ID of its Entity and a Type of either INTENT or COMMIT. It is assumed that a COMMIT with a given ID will always be preceded by one-or-more INTENTs with the same ID. When an INTENT is received, it should be grouped by its ID and a "buffer" for that group should be opened. The buffer should be "closed" when a COMMIT for the same group is received or a configured timeout has lapsed. The resulting buffers should be emitted.
Note that it is possible to receive multiple INTENTs before receiving a closing COMMIT. (Edit:) The bufferDuration should guarantee that any "opened" buffer is emitted after bufferDuration time has lapsed since the INTENT that opened the buffer was received, with or without a COMMIT.
My latest attempt at this is the following:
public EntityEventBufferFactory {
private final Duration bufferDuration;
public EntityEventBufferFactory(Duration bufferDuration) {
this.bufferDuration = bufferDuration;
}
public Flux<List<EntityEvent>> createGroupBufferFlux(Flux<EntityEvent> eventFlux) {
return eventFlux.groupBy(EntityEvent::getId)
.map(groupedFlux -> createGroupBuffer(groupedFlux))
.flatMap(Function.identity());
}
protected Flux<List<EntityEvent>> createGroupBuffer(Flux<EntityEvent> groupFlux) {
return groupFlux.publish().buffer(groupFlux.filter(this::shouldOpenBufferOnEvent), createGroupBufferCloseSelector(groupFlux));
}
protected Function<EntityEvent, Publisher<EntityEvent>> createGroupBufferCloseSelector(Flux<EntityEvent> groupFlux) {
return event -> Flux.firstEmitting(Flux.just(event).delay(bufferDuration), groupFlux.filter(this::shouldCloseBufferOnEvent).publish());
}
protected boolean shouldOpenBufferOnEvent(EntityEvent entityEvent) {
return entityEvent.getEventType() == EventType.INTENT;
}
protected boolean shouldCloseBufferOnEvent(EntityEvent entityEvent) {
return entityEvent.getEventType() == EventType.COMMIT;
}
}
And here is the test I am attempting to get passing:
#Test
public void entityEventsCanBeBuffered() throws Exception {
FluxProcessor<EntityEvent, EntityEvent> eventQueue = UnicastProcessor.create();
Duration bufferDuration = Duration.ofMillis(250);
Flux<List<EntityEvent>> bufferFlux = new EntityEventBufferFactory(bufferDuration).createGroupBufferFlux(eventQueue);
bufferFactory.setBufferDuration(bufferDuration);
List<List<EntityEvent>> buffers = new ArrayList<>();
bufferFlux.subscribe(buffers::add);
EntityEvent intent = new EntityEvent();
intent.setId("SOME_ID");
intent.setEventType(EventType.INTENT);
EntityEvent commit = new EntityEvent();
commit.setId(intent.getId());
commit.setEventType(EventType.COMMIT);
eventQueue.onNext(intent);
eventQueue.onNext(commit);
eventQueue.onNext(intent);
eventQueue.onNext(commit);
Thread.sleep(500);
assertEquals(2, buffers.size());
assertFalse(buffers.get(0).isEmpty());
assertFalse(buffers.get(1).isEmpty());
}
With this test, I get two emitted buffers, but they are both empty. You'll note that after digging around, I had to add .publish() at certain points to not get an Exception from Reactor saying This processor allows only a single Subscriber. The answer to this question, RxJava: "java.lang.IllegalStateException: Only one subscriber allowed!", is what led me to that approach.
I'm currently using Reactor, but I think this translates 1-to-1 with RxJava using Observable and methods of the same names.
Any thoughts?
I think that is the definitive use case of Rx groupBy. From the documentation:
Groups the items emitted by a Publisher according to a specified criterion, and emits these grouped items as GroupedFlowables. The emitted GroupedPublisher allows only a single Subscriber during its lifetime and if this Subscriber cancels before the source terminates, the next emission by the source having the same key will trigger a new GroupedPublisher emission.
In your case, this criterion is the ID, and on each GroupedPublisher emitted you takeUntil the type is COMMIT:
source
.groupBy(EntityEvent::getId)
.flatMap(group ->
group
.takeUntil(Flowable.timer(10,TimeUnit.SECONDS))
.takeUntil(this::shouldCloseBufferOnEvent)
.toList())
Edit: added time condition.
Thank you to Tassos Bassoukos for the input. The following Reactor code works for me:
public EntityEventBufferFactory {
private final Duration bufferDuration;
public EntityEventBufferFactory(Duration bufferDuration) {
this.bufferDuration = bufferDuration;
}
#Override
public Flux<List<EntityEvent>> create(Flux<EntityEvent> eventFlux) {
return eventFlux.groupBy(EntityEvent::getId)
.map(this::createGroupBuffer)
.flatMap(Function.identity());
}
protected Mono<List<EntityEvent>> createGroupBuffer(Flux<EntityEvent> groupFlux) {
return groupFlux.take(bufferDuration)
.takeUntil(this::shouldCloseBufferOnEvent)
.collectList();
}
protected boolean shouldCloseBufferOnEvent(EntityEvent EntityEvent) {
return EntityEvent.getEventType() == EventType.COMMIT;
}
}
i would like to implement a Pollingservice which calls a REST Api every nDelay Seconds and notify all subscribers if the data has been changed. Now i have a little problem with my code since it always returns a value to my Consumer, even if the data has not been changed.
private Observable<List<HueLight>> pollingLightsObservable = null;
public Observable<List<HueLight>> getPollingLightsObservable() {
if (pollingLightsObservable == null) {
pollingLightsObservable = Observable.fromCallable(
() -> LightManager
.getInstance(context)
.getLights()
.blockingSingle())
// .distinctUntilChanged( (l1, l1) -> !l1.equals(l2) )
.repeatWhen(o -> o.concatMap(v -> Observable.timer(1, TimeUnit.SECONDS)));
}
return pollingLightsObservable;
}
Enabling or using the distinctUntilChanged dont change anything. Doesnt matter if i put it before or after my repeatWhen.
Since my RetroFit Call returns an Observable, i have to use blockingSingle(). Using the Observable directly it leads into a return of "4, 8, 12, 16, .." items with this sample:
LightManager.getInstance(context).getLights()
.repeatWhen(o -> o.concatMap(v -> Observable.timer(1, TimeUnit.SECONDS)))
Currently i subscribe from different classes/activites with
this.lightChangeSubscriber = PollingManager
.getInstance(getContext())
.getPollingLightsObservable()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(hueLights -> {
{
Log.d(TAG, "Lights received successfully! Size=" + hueLights.size());
}
});
I would lovely avoid using interfaces and timer to create the polling. What would you recommend ?
what about using some custom filter?
public class FilterDuplicateHueConfig implements Predicate<HueConfig> {
private HueConfig lastVal;
#Override
public boolean test(HueConfig newVal) {
if(lastVal == null) {
lastVal = newVal;
return true;
}
... compare here the two values and return true/false appropriately...
}
}
(That title alone should cause people to come out of the woodwork to bash me with clubs, but hear me out).
I have a use case where I need to return a value from a asynchronous call. (I'm using GWT-Platform, but the concepts are the same.) I declared a final JavaScriptObject array, then assigned the value within the AsyncCallback. However, I need to return the value, and the method returns before the AsyncCallback completes. Therefore, I need to block somehow until the AsyncCallback completes. I need the returned value in another method, or I'd just do what I need to in onSuccess().
I've tried loops, Timers, and a few other methods with no luck. Can anyone help?
#Override
public JavaScriptObject doGetWhereAmIMarker(final double lng, final double lat) {
final JavaScriptObject[] markerArray = new JavaScriptObject[1]; // ugly hack, I know
dispatch.execute(new GetLocationDescriptionsAction(lng, lat), new AsyncCallback<GetLocationDescriptionsResult>() {
#Override
public void onFailure(Throwable caught) {
caught.printStackTrace();
}
#Override
public void onSuccess(GetLocationDescriptionsResult result) {
Map<String, Location> resultMap = result.getResult();
StringBuffer message = new StringBuffer();
for (String key : resultMap.keySet()) {
message.append(key).append(": ").append(resultMap.get(key)).append("\n");
}
Map tempMap = new HashMap();
tempMap.put("TITLE","Location Information");
tempMap.put("LAT", lat);
tempMap.put("LNG", lng);
tempMap.put("CONTENTS", message.toString());
JavaScriptObject marker = GoogleMapUtil.createMarker(tempMap);
markerArray[0] = marker;
if (markerArray[0] != null) {
GWT.log("Marker Array Updated");
}
}
});
return markerArray[0];
}
UPDATE: As requested, here is the code that calls doGetWhereIAmMarker(). I've tried having a separate native method with the Google Map object (as a JavaScriptObject) as a parameter, but it appears that passing that object between native methods kills the ability to update said object.
public native void initMap(JavaScriptObject mapOptions, JavaScriptObject bounds, JavaScriptObject border, JsArray markerArray, Element e) /*-{
// create the map and fit it within the given bounds
map = new $wnd.google.maps.Map(e, mapOptions);
if (bounds != null) {
map.fitBounds(bounds);
}
// set the polygon for the borders
if (border != null) {
border.setMap(map);
}
// set up the info windows
if (markerArray != null && markerArray.length > 0) {
var infoWindow = new $wnd.google.maps.InfoWindow({
content:"InfoWindow Content Goes Here"
});
for (var i = 0; i < markerArray.length; i++) {
var marker = markerArray[i];
marker.setMap(map);
$wnd.google.maps.event.addListener(marker, 'click', function() {
infoWindow.setContent(marker.content);
infoWindow.open(map, this);
});
}
}
// need to reference the calling class inside the function(), so set a reference to "this"
var that = this;
$wnd.whereAmI=function(lng, lat) {
that.#org.jason.mapmaker.client.view.MapmakerMapViewImpl::whereAmI(DD)(lng,lat);
}
$wnd.google.maps.event.addListener(map, 'click', function(event) {
var lat = event.latLng.lat();
var lng = event.latLng.lng();
$wnd.whereAmI(lng, lat);
});
}-*/;
At some point I had to do something similar but eventually I eliminated that code in favor of asynchronous stuff. Therefore, I can't give exact code that you need to use but only few pointers on how to approach it.
Firstly, this blog describes how to do synchronous AJAX using javascript.
Second, you must provide support for sync calls. The problem is that GWT does not support the parameter that provides synchronous AJAX calls. Most likely is that they don't want to encourage its use. Therefore you would need to use JSNI to add appropriate method to XMLHttpRequest (which you probably would extend) and then to RequestBuilder (also should extend it).
Finally, amend your service using extended RequestBuilder. Something like
((ServiceDefTarget)service).setRpcRequestBuilder(requestBuilder);
And in conclusion - from the same blog post (slightly out of context):
Because of the danger of a request getting lost and hanging the browser,
synchronous javascript isn't recommended for anything outside of
(onbefore)unload event handlers.
I Think its all fate....
We cannot do in Gwt to catch the Response and Send it, because immediately after the request is send the next method starts executing, neither bothering the response
Still however they satisfy us to use the Timers, is what i think...
Timer t = new Timer() {
#Override
public void run() {
Window.alert("Nifty, eh?");
}
};
t.schedule(5000);