How to execute RoboSpice requests synchronously?
Asked Answered
P

3

7

How to execure RoboSpice requests synchronously? Is it possible? I would like to use RoboSpice in IntentService.

Edit:

From time to time comes the need to execute something synchronously e.g. in service. In my recent project I have to queue some different types of requests and I wanted to use IntentService with RoboSpice. In my case when I'm executing different requests I need to wait for results from request1 then pass data from it to request2 and execute it.

I want to have some kind of batch request queue. Lets say we have two types of request: request1 and request2. request2 needs data fetched by request1:

  1. execute request1
  2. wait
  3. get data fetched by request1 and pass to request2
  4. execute request2
  5. wait
  6. goto 1.

I wanted to use IntentService (queue) but it dies after starting requests because of async.

In my opinion using listeners or CountDownLatch isn't the best way.

Procambium answered 23/4, 2014 at 11:36 Comment(1)
I have the same problem, working on it too :(Liuka
D
7

As proposed by Riccardo Ciovatti on the RoboSpice mailing list, a direct answer to your question is :

final CountDownLatch latch = new CountDownLatch(1);

final YourRequest request = new YourRequest();
spiceManager.execute(request, new RequestListener<YourResponse>() {

    @Override
    public void onRequestFailure(SpiceException spiceException) {
        latch.countDown();
    }

    @Override
    public void onRequestSuccess(YourResponse response) {
        latch.countDown();
    }
});

latch.await();

But this isn't a good idiom to use unless it's being executed on a background thread. Asynchronous processing is an essential feature of RoboSpice; it keeps the UI thread free.

Dru answered 24/4, 2014 at 9:33 Comment(3)
The new question is not about a sync request but how to chain them. In that case, execute the second request from the listener of the first.Dru
@Dru I've been searching for a simple way to execute a RoboSpice request synchronously and have reluctantly settled on this pattern. Everywhere I see it discussed you disregard it as a valid requirement due to blocking the UI thread, but what if you're not executing it from the UI thread, in my case I'd like to do it in the performFiltering method of a Filter. This method is executed from a worker thread so there's no need to offload execution to the service. Seems like unnecessary overhead.Lushy
@darnmason, your comment is a little confusing to me. If you want to execute a request synchronously, just execute it's getDataFromNetwork method. In the end of your comment, you would like not to use a service to perform your request, but this is the essence of RS. If you want to chain the requests, then Ricardo's method is fine. But I agree, RS could provide an alternative approach where you get a Future of the result but it would be hard to get the fine-grained semantics that listener have with their different callbacks. Please submit a Github issue if you have a clear API to reach for RS.Dru
Z
1

Using RoboSpice in IntentService. Solved the problem with help of CountDownLatch. Let's say we have 2 different SpiceManagers and some syncMethods to execute in sequence in the IntentService.

Globals:

private final SpiceManager aSpiceManager =
       new SpiceManager(ASpiceService.class);
private final SpiceManager bSpiceManager =
       new SpiceManager(BSpiceService.class);

private CountDownLatch handleIntentLatch;

onHandleIntent: Before we execute syncMethodA - we init our CountDownLatch with 1. After executing syncMethodA we await() for countDown() on our latch. When later some method will call countDown() on our latch at least once - method onHandleIntent will continue it's execution and finish which will trigger IntentService onDestroy() callback.

@Override
protected void onHandleIntent(Intent intent) {
    handleIntentLatch = new CountDownLatch(1);

    syncMethodA();

    try {
        handleIntentLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

syncMethodA(): Suppose we need to launch some sync methods in sequence (syncMethodA which callback executes syncMethodB etc).

private void syncMethodA() {
    SpiceRequest requestA = new SpiceRequest();

    if (!aSpiceManager.isStarted()) {
        LogUtils.LOGD(TAG, "starting aSpiceManager");
        aSpiceManager.start(getApplicationContext());
    }

    aSpiceManager.execute(requestA , new RequestListener<ResponseA>() {
        @Override
        public void onRequestSuccess(final ResponseA responseA) {
            // SOME LOGIC

            syncMethodB();

            // SOME LOGIC

        @Override
        public void onRequestFailure(SpiceException spiceException) {
            handleIntentLatch.countDown();
            // SOME LOGIC
        }
    });
}

syncMethodB, syncMethodC etc are the same - in onRequestSuccess we start next syncMethodX. In on onRequestFailure we countDown() our latch (handleIntentLatch).

Very important!!! In last syncMethodX in sequence (after which completion we want onHandleIntent method to continue it's execution and finish which will result IntentService to stop) - we countDown() our latch in onRequestSuccess ALSO.

onDestroy: here we stop our SpinceManagers.

@Override
public void onDestroy() {
    super.onDestroy();
    LogUtils.LOGD(TAG, "onDestroy");
    shutDownSpiceManagers();
}

shutDownSpiceManagers:

private void shutDownSpiceManagers() {
    if (aSpiceManager.isStarted()) {
        LogUtils.LOGD(TAG, "stopping aSpiceManager");
        aSpiceManager.shouldStop();
    }
    if (bSpiceManager.isStarted()) {
        LogUtils.LOGD(TAG, "stopping bSpiceManager");
        bSpiceManager.shouldStop();
     }
}

All should be OK now: no leaked Context, SpiceManagers will be killed in onDestroy AND ONLY AFTER callbacks are resolved.

Zoom answered 3/10, 2014 at 13:30 Comment(0)
V
0

If you don't want exactly synchronous execution, but only want requests to be executed sequentially in some order, you may write your own simple class which will just execute request B when request A succeeded or failed. Like that: https://github.com/Deepscorn/Shared/blob/master/app/src/main/java/com/gamelift/shared/request/base/RequestSequenceExecutor.java. So, code will look like:

sequenceExecutor.setRequest(new FirstRequest());
sequenceExecutor.setOnSuccessListener(FirstRequest.class, new OnSuccessListener {
   public void onSuccess() {
     sequenceExecutor.setRequest(new SecondRequest());
   }
};

sequenceExecutor.setOnFaulListener(FirstRequest.class, new OnFailListener {
   public void onFail() {
     sequenceExecutor.setRequest(new OnFirstFailRequest());
   }
};

sequenceExecutor.setOnSuccessListener(SecondRequest.class, new OnSuccessListener {
   public void onSuccess() {
     notifyDone();
     return;
   }
};

sequenceExecutor.setDefaultOnFailListener(new OnFailListener {
   public void onFail(Exception e) {
     notifyDone();
     log(e);
     return;
   }
};
sequenceExecutor.execute() //starts execution
Verdi answered 10/4, 2015 at 12:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.