How to perform "RXJava 2 queue jobs" until value changes? - android

I'm using RXJava2 in my Android app, and I have a somewhat peculiar scenario.
I want to perform unknown amount of jobs (determined by the user), but I want them to start only after a certain value has changed.
My specific requirement is to use a Socket for server communication,
and the flow is the following:
User requests some data - data is retrieved by sending data to the socket and wait for the response.
The module that communicates with the server should open a Socket connection, and only after the connection established, it may fetch the requested data.
While Socket attempt to connect, the user may request some more data.
After the connection established successfully the module should perform all the requests sent by the user while connection process was in progress.
The module also should publish the results that came for each data sent to the socket.
How can this be accomplished using RXJava2?

You could use an UnicastSubject for the queue part and do some flatMap-ping once the connection is established:
UnicastSubject<String> userRequests = new UnicastSubject.create();
Single.fromCallable(() -> new Socket("server", port))
.subscribeOn(Schedulers.io())
.flatMapObservable(socket -> {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte[] responseBuffer = new byte[4096];
return userRequests
.observeOn(Schedulers.io())
.map(request -> {
out.write(request.getBytes());
int n = in.read(responseBuffer);
if (n >= 0) {
return new String(responseBuffer, 0, n);
}
throw new IOException("Socket closed while waiting for response");
})
.doFinally(() -> socket.close());
});
Since you are working on the Socket level, it is your responsibility to work out the proper encoding of the requests to be written and the proper decoding of the response to be read (i.e., how long (in bytes) the response is to a particular request).

I believe you would need FlowableTransformers.valve() for this, from RxJava2Extensions.
It should work something like this
PublishSubject<String> jobs = PublishSubject.create().toSerialized();
BehaviorSubject<Boolean> isConnected = BehaviorSubject.createDefault(false);
CompositeDisposable disposables = new CompositeDisposable();
public void connect() {
disposables.add(socketService.subscribe((success) -> {
isConnected = true;
}));
}
public void addJob(String job) {
jobs.onNext(job);
}
public void executeQueuedTasks() {
disposables.add(jobs
.toFlowable(BackpressureStrategy.BUFFER)
.compose(FlowableTransformers.valve(isConnected))
.subscribeWith(new DisposableObserver<>() {
...
})
);
}
public void destroy() {
disposables.clear();
}
}
But the UnicastSubject sample above is more likely to work, I wrote this off the top of my head.

Related

HTTP requests received in wrong time sequence

I am creating an Android app that sends http requests contains IMU data every 20ms using Handler and Runnable.
public void onClickLogData(View view){
Log.d(TAG,"onClickLogData");
final OkHttpClient client = new OkHttpClient();
Handler handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
if (Running) {
handler.postDelayed(this, 20);
String url = "http://192.168.86.43:5000/server";
Log.d(TAG, String.valueOf(time));
RequestBody body = new FormBody.Builder()
.add("Timestamp", String.valueOf(time))
.add("accx", String.valueOf(accx))
.add("accy", String.valueOf(accy))
.add("accz", String.valueOf(accz))
.add("gyrox", String.valueOf(gyrox))
.add("gyroy", String.valueOf(gyroy))
.add("gyroz", String.valueOf(gyroz))
.add("magx", String.valueOf(magx))
.add("magy", String.valueOf(magy))
.add("magz", String.valueOf(magz))
.build();
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
final Call call = client.newCall(request);
call.enqueue(new Callback() {
#Override
public void onFailure(#NonNull Call call, #NonNull IOException e) {
Log.i("onFailure", e.getMessage());
}
#Override
public void onResponse(#NonNull Call call, #NonNull Response response)
throws IOException {
assert response.body() != null;
String result = response.body().string();
Log.i("result", result);
}
});
} else {
handler.removeCallbacks(this);
}
}
};
handler.postDelayed(runnable, 1000);
}
And the data are received and stored on my laptop.
with open('imu.csv','w') as csv_file:
writer = csv.writer(csv_file)
writer.writerow(['Timestamp','accx','accy','accz','gyrox','gyroy','gyroz','magx','magy','magz'])
app = Flask(__name__)
#app.route('/server', methods=['GET','POST'])
def server():
r = request.form
data = r.to_dict(flat=False)
t = int(str(data['Timestamp'])[2:-2])
print(t)
accx = float(str(data['accx'])[2:-2])
accy = float(str(data['accy'])[2:-2])
accz = float(str(data['accz'])[2:-2])
gyrox = float(str(data['gyrox'])[2:-2])
gyroy = float(str(data['gyroy'])[2:-2])
gyroz = float(str(data['gyroz'])[2:-2])
magx = float(str(data['magx'])[2:-2])
magy = float(str(data['magy'])[2:-2])
magz = float(str(data['magz'])[2:-2])
imu_data = [t,accx,accy,accz,gyrox,gyroy,gyroz,magx,magy,magz]
with open('imu.csv','a+') as csv_file:
writer = csv.writer(csv_file)
writer.writerow(imu_data)
return("ok")
if __name__ == '__main__':
app.run(host='0.0.0.0')
The requests are sent in chronological order on Android side as Log indicates, however on the receiving side many of the requests are received in wrong time sequence. enter image description here
It seems that this happens more frequently as time goes. What possibly could be the cause of this and where should I be looking at?
All sorts of things. Requests are sent over a network. They can take different paths to get there each time. Requests can even get lost. Using TCP you'd automatically resend a lost request, but then it would be even more out of order. They can be delayed in the network in different bridges and routers. There is no promise over the internet that different requests will be received in order. That's only a promise over a single socket using TCP- and that is only possible with a lot of work (basically keeping track of every packet sent and received and waiting until you have them in order to send it to the app). If your architecture requires you to receive them in order, your architecture cannot possibly work over the internet.
If you do need an ordering on the server, either embed a request number that's monotonically increasing, or embed a timestamp in the request.

Socket.io: Error: Callbacks are not supported when broadcasting at Socket.emit

I successed to send ack to android client from nodejs server but I don't succeed to do reverse. I have this error: Callbacks are not supported when broadcasting at Socket.emit
Serveur nodejs:
socket.broadcast.to(socketid).emit('message', data, callThis);
//this function is executed when client calls it
function callThis (dataFromClient){
console.log("Call back fired: " + dataFromClient);
}
client android:
socket.on("message", new Emitter.Listener() {
#Override
public void call(Object... args) {
Ack ack = (Ack) args[args.length - 1];
ack.call();
JSONObject data = (JSONObject) args[0];
.....
}
}
What can I do to resolve this problem?
Basically support the answer of #Xeoncross. When a connection came, just saved the socket into a map, like below
this.connections = new Map<string, SocketIO.Socket>()
this.server.on("connection", (socket: SocketIO.Socket) => {
this.connections.set(socket.id, socket)
})
Then use a loop to send all users individually
public broadcast(msg: string) {
for(const socket of this.connections.values()) {
socket.emit("block", msg, (confirm: string) => {
console.log("confirmation msg: ", confirm)
})
}
}
As the error says, "Callbacks are not supported when broadcasting". It doesn't look like you are broadcasting though, as you are trying to send to a single client. So assuming socket is an actual client socket instance you can change your code:
socket.broadcast.to(socketid).emit('message', data, callThis);
to just send to that one person
socket.emit('message', data, callThis);

PendingResult.setResultCallback() always returns the statusCode success

if i connect my google watch with a mobile device successfully, and then disable the bluetooth connection (for test reasons) and make a google api client call to my mobile device, the pending result always returns the status code success, even if its not successfull because there is no more connection
async task for the request
class DataTask extends AsyncTask<Node, Void, Void> {
#Override
protected Void doInBackground(Node... nodes) {
Gson gson = new Gson();
Request requestObject = new Request();
requestObject.setType(Constants.REQUEST_TYPE);
String jsonString = gson.toJson(requestObject);
PutDataMapRequest dataMap = PutDataMapRequest.create(Constants.PATH_REQUEST);
dataMap.setUrgent();
dataMap.getDataMap().putString(Constants.KEY_REQUEST, jsonString);
PutDataRequest request = dataMap.asPutDataRequest();
DataApi.DataItemResult dataItemResult = Wearable.DataApi
.putDataItem(googleApiClient, request).await();
boolean connected = googleApiClient.isConnected();
PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(googleApiClient, request);
pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
#Override
public void onResult(#NonNull DataApi.DataItemResult dataItemResult) {
com.google.android.gms.common.api.Status status = dataItemResult.getStatus();
DataItem dataItem = dataItemResult.getDataItem();
boolean dataValid = dataItemResult.getDataItem().isDataValid();
boolean canceled = status.isCanceled();
boolean interrupted = status.isInterrupted();
float statusCode = status.getStatusCode();
if(status.isSuccess()){ // expected to be false because there is no bluetooth connection anymore
Log.d(TAG, "Success");
}else{
Log.d(TAG, "Failure");
}
}
});
return null;
}
}
why do i not get a false for status.isSuccess?
the only solution i found is to write following code inside the AsyncTask:
Wearable.NodeApi.getConnectedNodes(googleApiClient).await().getNodes();
if(connectedNodes.size() == 0){
// no connection
}
is it not possible to check if the request was successfully inside the ResultCallback?
I believe that the getStatus() call for DataItemResult is only indicating whether the call was successfully passed off to the Data API, not whether it was successfully relayed to another node. The Data API is asynchronous - it's a "store and forward" architecture - so it's not reasonable to expect it to notify you immediately of successful delivery.
In fact, I don't think that there is a way to determine from the Data API when your DataItem has been delivered at all. Your getConnectedNodes technique is only telling you that the watch is connected, not that the data has been delivered. If you need proof of delivery, you'll probably have to implement that yourself, perhaps using the Message API.
One other note: given you've wrapped your code in an AsyncTask, there's no need to use PendingResult.setResultCallback. You can simply await the result inline: http://developer.android.com/training/wearables/data-layer/events.html#sync-waiting

Mock Api Responses in Android Testing

I'm looking for a way to mock api responses in android tests.
I have read the roboelectric could be used for this but I would really appreciate any advice on this.
After a small bit of looking around on the web I have found MockWebServer to be what I was looking for.
A scriptable web server for testing HTTP clients. This library makes it easy to test that your app Does The Right Thing when it makes HTTP and HTTPS calls. It lets you specify which responses to return and then verify that requests were made as expected.
To get setup just add the following to your build.gradle file.
androidTestCompile 'com.google.mockwebserver:mockwebserver:20130706'
Here is a simple example taking from their GitHub page.
public void test() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
// Schedule some responses.
server.enqueue(new MockResponse().setBody("hello, world!"));
// Start the server.
server.play();
// Ask the server for its URL. You'll need this to make HTTP requests.
URL baseUrl = server.getUrl("/v1/chat/");
// Exercise your application code, which should make those HTTP requests.
// Responses are returned in the same order that they are enqueued.
Chat chat = new Chat(baseUrl);
chat.loadMore();
assertEquals("hello, world!", chat.messages());
// Shut down the server. Instances cannot be reused.
server.shutdown();
}
Hope this helps.
MockWebServer didn't work for me with AndroidTestCase. For instance, ECONNREFUSED error happened quite randomly (described in https://github.com/square/okhttp/issues/1069). I didn't try Robolectric.
As of OkHttp 2.2.0, I found an alternative way which worked well for me: Interceptors. I placed the whole mock response inside a json file stored on androidTest/assets/, say, 'mock_response.json'. When I instanced an OkHttp for testing, I exposed an Interceptor which I would rewrite the incoming response. Basically, body() would instead stream the data in 'mock_response.json'.
public class FooApiTest extends AndroidTestCase {
public void testFetchData() throws InterruptedException, IOException {
// mock_response.json is placed on 'androidTest/assets/'
final InputStream stream = getContext().getAssets().open("mock_response.json");
OkHttpClient httpClient = new OkHttpClient();
httpClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
return new Response.Builder()
.protocol(Protocol.HTTP_2)
// This is essential as it makes response.isSuccessful() returning true.
.code(200)
.request(chain.request())
.body(new ResponseBody() {
#Override
public MediaType contentType() {
return null;
}
#Override
public long contentLength() {
// Means we don't know the length beforehand.
return -1;
}
#Override
public BufferedSource source() {
try {
return new Buffer().readFrom(stream);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
})
.build();
}
});
FooApi api = new FooApi(httpClient);
api.fetchData();
// TODO: Let's assert the data here.
}
}
This is now even easier with Mockinizer which makes working with MockWebServer easier:
val mocks: Map<RequestFilter, MockResponse> = mapOf(
RequestFilter("/mocked") to MockResponse().apply {
setResponseCode(200)
setBody("""{"title": "Banana Mock"}""")
},
RequestFilter("/mockedError") to MockResponse().apply {
setResponseCode(400)
}
)
Just create a map of RequestFilter and MockResponses and then plug it into your OkHttpClient builder chain:
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.mockinize(mocks) // <-- just plug in your custom mocks here
.build()

Android USB host interrupt transfer: how to TX and RX at any time

I'm trying to communicate with a USB device that uses interrupt transfer for communications.
it is not a polled device, either side may send at any time. All the examples I find seem to be poll-response where you send data first, wait for the send to complete, then wait for the response, process it and then go back to sending data again.
My code is modeled after the following which I found here on stackoverflow (I'm showing the original I based it on because my own code has a lot more going on and is less compact)
boolean retval = request.queue(buffer, 1);
if (mConnection.requestWait() == request) {
// wait for confirmation (request was sent)
UsbRequest inRequest = new UsbRequest();
// URB for the incoming data
inRequest.initialize(mConnection, mEndpointIn);
// the direction is dictated by this initialisation to the incoming endpoint.
if(inRequest.queue(buffer, bufferMaxLength) == true){
mConnection.requestWait();
// wait for this request to be completed
// at this point buffer contains the data received
}
}
The second requestWait() will block until something arrives, so I can't do another TX operation until I receive something! What am I missing?
You said: "The second requestWait() will block until something arrives, so I can't do another TX operation until I receive something"
Having written code of my own also based on the example you show, I think I understand where you are confused: The second requestWait() will return for any USB operation, not just the one that preceeded it. (from the Android API documentation "Note that this may return requests queued on multiple UsbEndpoints")
So if you queue a Send request even while you are waiting, your "receive waitRequest" will return, but for the Send endpoint. You should always check the endpoint of the result of waitRequest, or compare it to the initial request itself. If it matches inRequest, then it's actually the receive operation you were blocking on. If it doesn't match, compare it to your Send request (or in my example code below, I simply assume that it's a send response and ignore it)
You will need to queue send and receive requests from different methods or threads however rather than in the same loop as is implied by the code you supplied.
Here is the code from my own project (be aware that my app is running into heap corruptions, so the code below may not be perfect, but it does allow me to send even while a receive operation is pending)
So here is my receive loop, you'll see the similarities with your code:
while(mUsbDevice != null ) {
if (inRequest.queue(buffer, BUFFER_SIZE) == true) {
// (mUsbConnection.requestWait() is blocking
if (mUsbConnection.requestWait() == inRequest){
// this is an actual receive
// do receive processing here (send to conusmer)
} else{
Log.d(TAG, "mConnection.requestWait() returned for a different request (likely a send operation)");
}
} else {
Log.e(TAG, "failed to queue USB request");
}
buffer.clear();
}
I do the sending form another thread which uses messages to queue incoming send requests:
mHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 1) { // 1 means send a 64 bytes array in msg.obj
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
final byte[] array = (byte[]) msg.obj;
buffer.clear();
buffer.put( array );
UsbRequest outRequest = new UsbRequest();
outRequest.initialize(mUsbConnection, mUsbEndpointOut);
outRequest.queue(buffer, BUFFER_SIZE);
Log.d(L.TAG, "Queueing request:"+outRequest);
// don't do a mConnection.requestWait() here, ReceiveThread is already listening
} else if (msg.what == 2) { // 2 means exit
Log.d(L.TAG, "SenderThread::handleMessage(): terminate");
Looper.myLooper().quit();
} else {
Log.e(L.TAG, "SenderThread::handleMessage(): unknow message type: " + msg.what);
}
}
};

Categories

Resources