I have a function in iOS app that uses dispatch_group
to group multiple rest request:
static func fetchCommentsAndTheirReplies(articleId: String, failure: ((NSError)->Void)?, success: (comments: [[String: AnyObject]], replies: [[[String: AnyObject]]], userIds: Set<String>)->Void) {
var retComments = [[String: AnyObject]]()
var retReplies = [[[String: AnyObject]]]()
var retUserIds = Set<String>()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
Alamofire.request(.GET, API.baseUrl + API.article.listCreateComment, parameters: [API.article.articleId: articleId]).responseJSON {
response in
dispatch_async(queue) {
guard let comments = response.result.value as? [[String: AnyObject]] else {
failure?(Helper.error())
return
}
print(comments)
retComments = comments
let group = dispatch_group_create()
for (commentIndex, comment) in comments.enumerate() {
guard let id = comment["_id"] as? String else {continue}
let relevantUserIds = helperParseRelaventUserIdsFromEntity(comment)
for userId in relevantUserIds {
retUserIds.insert(userId)
}
retReplies.append([[String: AnyObject]]())
dispatch_group_enter(group)
Alamofire.request(.GET, API.baseUrl + API.article.listCreateReply, parameters: [API.article.commentId: id]).responseJSON {
response in
dispatch_async(queue) {
if let replies = response.result.value as? [[String: AnyObject]] {
for (_, reply) in replies.enumerate() {
let relevantUserIds = helperParseRelaventUserIdsFromEntity(reply)
for userId in relevantUserIds {
retUserIds.insert(userId)
}
}
retReplies[commentIndex] = replies
}
dispatch_group_leave(group)
}
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
success(comments: retComments, replies: retReplies, userIds: retUserIds)
}
}
}
As you can see from my code, I fetch all the comments
under the same article
, then fetch coresponding replies
under each comment
. After all requests are done, I invoke my success
callback. This can be achieved using GCD's dispatch_group
.
Now I am migrating the same functionality to android.
public static void fetchCommentsAndTheirReplies(Context context, String articleId, final StringBuffer outErrorMessage, final Runnable failure, final ArrayList<JSONObject> outComments, final ArrayList<ArrayList<JSONObject>> outReplies, final HashSet<String> outUserIds, final Runnable success) {
final RequestQueue queue = Volley.newRequestQueue(context);
HashMap<String, String> commentParams = new HashMap<>();
commentParams.put(API.article.articleId, articleId);
JsonArrayRequest commentRequest = new JsonArrayRequest(Request.Method.GET, API.baseUrl + API.article.listCreateComment, new JSONObject(commentParams), new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
try {
for (int i = 0; i < response.length(); i++) {
JSONObject comment = response.getJSONObject(i);
outComments.add(comment);
outUserIds.addAll(helperParseRelaventUserIdsFromEntity(comment));
outReplies.add(new ArrayList<JSONObject>());
//TODO: DISPATCH_GROUP?
String id = comment.getString("_id");
HashMap<String, String> replyParams = new HashMap<>();
replyParams.put(API.article.commentId, id);
final int finalI = i;
JsonArrayRequest replyRequest = new JsonArrayRequest(Request.Method.GET, API.baseUrl + API.article.listCreateReply, new JSONObject(replyParams), new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
try {
for (int j = 0; j < response.length(); j++) {
JSONObject reply = response.getJSONObject(j);
outUserIds.addAll(helperParseRelaventUserIdsFromEntity(reply));
outReplies.get(finalI).add(reply);
}
} catch (JSONException ex) {}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {}
});
queue.add(replyRequest);
}
success.run();
} catch (JSONException ex) {}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
outErrorMessage.append(error.getMessage());
failure.run();
}
});
queue.add(commentRequest);
}
Note that I am using success
is executed right after I get all the comments
, and before getting all the replies
.
So how can I group them and delay the response?
I am working on the hairy implementation like
taskCount++;
if (taskCount == totalCount) {
success.run();
}
in reply block, but it seems very tedious.
You can simply do it with this class I made to mimic the iOS behavior. Call enter() and leave() the same way you did in iOS with dispatch_group_enter and dispatch_group_leave and call notify() just after the requests you want to group, just like dispatch_group_notify. It also uses runnable the same way iOS uses blocks :
public class DispatchGroup {
private int count = 0;
private Runnable runnable;
public DispatchGroup()
{
super();
count = 0;
}
public synchronized void enter(){
count++;
}
public synchronized void leave(){
count--;
notifyGroup();
}
public void notify(Runnable r) {
runnable = r;
notifyGroup();
}
private void notifyGroup(){
if (count <=0 && runnable!=null) {
runnable.run();
}
}
}
Hope it helps ;)
Here is the Kotlin version of Damien Praca's answer. This will allow you to use Kotlin lambdas like this.
val dispatchGroup = DispatchGroup()
dispatchGroup.enter()
// Some long running task
dispatchGroup.leave()
dispatchGroup.notify {
// Some code to run after all dispatch groups complete
}
class DispatchGroup {
private var count = 0
private var runnable: (() -> Unit)? = null
init {
count = 0
}
@Synchronized
fun enter() {
count++
}
@Synchronized
fun leave() {
count--
notifyGroup()
}
fun notify(r: () -> Unit) {
runnable = r
notifyGroup()
}
private fun notifyGroup() {
if (count <= 0 && runnable != null) {
runnable!!()
}
}
}
There is no direct analogue of dispatch_group
in plain Java or Android. I can recommend a few rather sophisticated techniques to produce a really clean and elegant solution if you're ready to invest some extra time in it. It's not gonna be one or two lines of code, unfortunately.
Use RxJava
with parallelization. RxJava
provides a clean way to dispatch multiple tasks, but it works sequentially by default. See this article to make it execute tasks concurrently.
Although this is not exactly the intended usecase, you can try the ForkJoinPool to execute your group of tasks and recieve a single result afterwards.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With