Search code examples
androidfirebaseasynchronouscallbackandroid-asynctask

Callback functions that depend on one another in Android Studio - a more elegant solution


I have 2 callback functions in my app, one of which requires the other one's result, so I want them to execute asynchronously. The scenario is this:

  1. I obtain the device's token with FirebaseInstanceId.getInstance().getInstanceId(). That's the 1st callback.

  2. When I have the token, I use it in accessing the Firebase Realtime DB to get the user's data. That's the 2nd callback.

In my current solution, I'm using AsyncTask with a Semaphore in the doInBackground function, like so:

private class AsyncOp extends AsyncTask<Void, Void, String> {

        @Override
        protected String doInBackground(Void... voids) {
            final Semaphore semaphore = new Semaphore(0);
            FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(new OnSuccessListener<InstanceIdResult>() {
                @Override
                public void onSuccess(InstanceIdResult instanceIdResult) {
                    token = instanceIdResult.getToken();
                    JSONObject requestObject = new JSONObject();
                    try {
                        requestObject.put("token", token);
                        semaphore.release();
                    } catch (JSONException e) {
                        Log.e(TAG_MAINACTIVITY, token);
                        semaphore.release();
                    }
                    JsonObjectRequest req = new JsonObjectRequest(Request.Method.POST, REQUEST_URL + "token",
                            requestObject, new Response.Listener<JSONObject>() {
                        @Override
                        public void onResponse(JSONObject response) {
                            Log.i(TAG_MAINACTIVITY, "Token saved successfully");
                        }
                    },
                            new Response.ErrorListener() {
                                @Override
                                public void onErrorResponse(VolleyError error) {
                                    Log.e(TAG_MAINACTIVITY, "Failed to save token - " + error);
                                }
                            });

                    _queue.add(req);
                }
            });

            try {
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return token;
        }

        @Override
        protected void onPostExecute(String token) {
            getUser(token);
        }
    }

 public void getUser(String token) {
        db.child("users").child(token).addListenerForSingleValueEvent(new ValueEventListener() {
            @SuppressLint("SetTextI18n")
            @Override
            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

                if (!dataSnapshot.exists()) {
                    // Navigate to fragment where new users can sign up:
                    goToSignup();
                } else {
                    currentUser = dataSnapshot.getValue(User.class);
                    assert currentUser != null;
                    updateHeadline(currentUser.getName()); // Update UI to display the user's name
                }
            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {
                Log.d(TAG_MAINACTIVITY, databaseError.getMessage());
            }
        });
    }

and in my OnCreate I execute as follows:

_queue = Volley.newRequestQueue(this);
AsyncOp getToken = new AsyncOp();
getToken.execute();

It works just fine, but I can't help but feel like I'm missing the whole point here and that there's a better solution that I can use. I've scoured other StackOverflow questions dealing with similar issues and found some suggestions, but I got tangled up trying to implement them, being pretty new to this whole ordeal (that's actually my first "real" project). I tried looking for something that would work like a Promise and found Future but I probably didn't do it properly since it didn't work. That's the only solution that worked for me, other than simply nesting getUser(token) inside the first callback. So if you have any ideas that would work for me, I'd be glad to hear it.


Solution

  • Firebase performs all network and disk I/O off the main thread. That means there is seldom a reason to run Firebase interactions in a background task.

    Aside from that, the flow is significantly more complicated when you use a semaphore. I'd consider just putting the call to get a user into the completion handler of the previous call.

    Combined that becomes:

    FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(new OnSuccessListener<InstanceIdResult>() {
        @Override
        public void onSuccess(InstanceIdResult instanceIdResult) {
            token = instanceIdResult.getToken();
            JSONObject requestObject = new JSONObject();
            requestObject.put("token", token);
    
            JsonObjectRequest req = new JsonObjectRequest(Request.Method.POST, REQUEST_URL + "token",
                    requestObject, new Response.Listener<JSONObject>() {
                @Override
                public void onResponse(JSONObject response) {
                    Log.i(TAG_MAINACTIVITY, "Token saved successfully");
                    getUser(token);
                }
            },
            new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.e(TAG_MAINACTIVITY, "Failed to save token - " + error);
                }
            });
            _queue.add(req);
        }
    });