Java Basics for Retrofit — Callbacks

Returning the result of a long-running function at a later time is an important aspect of programming. A lot of high-level programming languages have the asynchronousity build in (e.g. C# with delegates) or it’s part of the language nature that it even leads to a callback hell (Node.js). Java/Android doesn’t have first class support and has to rely on callbacks via interfaces. In this tutorial, we’ll take a closer look what they are and how Retrofit utilizes them.

Java Callbacks

Before we go into the details, let’s start with an easy description of callbacks from Paul Jakubik:

Callbacks are most easily described in terms of the telephone system. A function call is analogous to calling someone on a telephone, asking her a question, getting an answer, and hanging up; adding a callback changes the analogy so that after asking her a question, you also give her your name and number so she can call you back with the answer. Paul Jakubik

Hopefully this quote explains fairly well what callbacks are and how we use them in programming. Retrofit doesn’t make any phone calls, but has to fulfill a very similar function: network requests. Because those go to a server somewhere on the Internet, there is no guarantee if and when we’ll get a response. Thus, it makes sense to start the request (phone call) and rather take a notification when the response (answer phone call) is available than wait for the result.

This is important on Android in particular due to the fact that Android 4.0 or newer crashes the app when you execute network operations on the main thread! You’ve to move to some kind of asynchronous model. Unfortunately, there are not that many options on Android. Java doesn’t support lambda expressions or method references until Java 8. You, me and all of the Android developers won’t be able to use those for a long time. Thus, there’s currently only one common way of implementing callbacks: via interfaces.

Callbacks via Interfaces

Before we look at Retrofit’s implementation, let’s look at a simplified example. The scenario is the following: we’re implementing an admin app for the Future Studio platform. On the screen we’re currently working on we want to display how many students are enrolled in our University. We can look that up in the database, which is already part of the admin app. We only have to query it. Followingly, our code is simple:

public class UniversityAdminActivity extends AppCompatActivity {

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_university_admin);

        int studentCount = DatabaseModule.getStudentCount();
        displayStudentCount(studentCount);
    }

    private void displayStudentCount(int studentCount) {
        // todo implement the display of the data
    }

    ...

    public static class DatabaseModule {
        public static int getStudentCount() {
            // todo: implement real logic, this seems fake ;)
            Thread.sleep(5000);
            return 42;
        }
    }
}

However, we’ve noticed that the query is not very fast and takes a couple of seconds, which causes a noticeable UI freeze. It’s time to fix that by changing to an asynchronous model!

The new approach is the following: we’ll request the data from our database module and instead of blocking everything until we get a response, we’ll just update the data when we get the data asynchronously at some point in the future.

Because Java doesn’t have native callbacks, only callback interfaces, we need to define an interface, which will describe the result callback method:

public interface DatabaseQuery {  
    // this is the method that will be called when the database is done
    // the result (studentCount) will be passed back to our activity as a parameter
    void studentCountResult(int studentCount);
}

Of course, we also have to change our DatabaseModule to return the result asynchronously. Our method doesn’t have a direct return type (int) anymore. Instead, we’ll return void and accept a callback interface as parameter. We return the result indirectly via the callback interface method studentCountResult():

public static class DatabaseModule {  
    public static void getStudentCount(DatabaseQuery callback) {
        // todo: implement real logic, this seems fake ;)
        Thread.sleep(5000);
        callback.studentCountResult(42);
    }
}

Finally, when we request the data from our DatabaseModule we’ve to pass an instance of our callback interface. Often, the code is cleaner to just use an anonymous declaration instead of initializing a full (field) variable:

public class UniversityAdminActivity extends AppCompatActivity {

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_university_admin);

        // we pass an (anonymous) instance of the DatabaseQuery interface
        DatabaseModule.getStudentCount(new DatabaseQuery() {
            @Override
            public void studentCountResult(int studentCount) {
                // whenever the result is ready we can display it
                displayStudentCount(studentCount);
            }
        });
    }

    private void displayStudentCount(int studentCount) {
        // todo implement the display of the data
    }

    ...

    public static class DatabaseModule {
        public static void getStudentCount(DatabaseQuery callback) {
            // todo: implement real logic, this seems fake ;)
            Thread.sleep(5000);
            callback.studentCountResult(42);
        }
    }
}

Hopefully this example made it clearer how you can implement callback interfaces in Java and what elements are necessary. You’ll see them again in the next section, when we talk about the Retrofit callbacks.

Retrofit’s Request Callbacks

Due to the nature of mobile networks and the Internet, there is no guarantee of knowing when a server responds to your request. As explained earlier, callbacks are the only way of implementing network requests, if you start them on the UI thread. Consequently, Retrofit (and every other request library) utilizes callbacks. Let’s look at them in more detail.

The Call class is the starting point for every network request with Retrofit. Usually, you’ll want to execute the request asynchronously with the enqueue method. The method expects a typed implementation of Retrofit’s Callback class, which expects the implementation of two methods (onResponse and onFailure). We won’t go into more detail what those two methods mean, you can learn about them in our getting started with Retrofit tutorial.

public class User {  
    private int id;
    private String email;
    private String username;
}

...

Call call = userService.login();  
call.enqueue(new Callback() {  
    @Override
    public void onResponse(Call call, Response response) {
        // todo deal with returned data (user)
    }

    public void onFailure(Call call, Throwable t) {
        // todo deal with the failed network request
    }
});

Retrofit uses two different callback methods for the two possible outcomes of a network requests: either a failure or a successful request. Retrofit will call the appropriate callback method depending on the result. If the request was successful, Retrofit will also pass you the response of the server. Fantastic!

Summary

In this tutorial you’ve learned a lot of background on callbacks and how they’re implemented in Java. It’s necessary to comprehend the logic behind Java’s callback interfaces. You’ve seen the components in action in our Future Studio University admin app. Afterwards, you’ve gained a deeper understanding in Retrofit’s callbacks.

If you’ve feedback or a question, let us know in the comments or on twitter @futurestud_io.

Make it rock & enjoy coding!


PS: do you still have questions? Get our Retrofit book on Leanpub!

Please follow and like us:
0

Gurupriyan is a Software Engineer and a technology enthusiast, he’s been working on the field for the last 6 years. Currently focusing on mobile app development and IoT.