DEV Community

Cover image for Asynchronous queries in Android.
Tristan Elliott
Tristan Elliott

Posted on • Updated on

Asynchronous queries in Android.

Introduction

  • This series is going to be dedicated to the basic of Android development. Join me and let us try to build and understand some cool stuff. All the resources I used to create this post can be found on ticketnote HERE.

A quick word

  • This tutorial is going to be on, how to retrieve a single row from a Room database. I am creating it out of my general frustration on trying to find the answer on what seems to be a very simple and easy problem to solve. With this tutorial I hope you save you from spending 9 hours knee deep in documentation. With that being said lets get started.

  • I also want to point out that what your about to see is most definitely 100% spaghetti code. So if you notice any code that could be improved please let me know in the comments down below.

YouTube version

HERE

The DAO

  • When we are creating queries for a Room database we always start with the relevant data access object(DAO). Since we are not making a @Update,@Insert or @Delete query we know that we will being using SQL to query the database. The query that we create to return one row will be below:
    //CALL A QUERY TO GET ONLY A SPECIFIC CALF
    @Query("SELECT * FROM calf_table WHERE id = :calfId")
    public Calf getCalf(int calfId);
Enter fullscreen mode Exit fullscreen mode
  • If any of the calf and cow references are confusing to you, don't worry they are just named that way because this code is all from my app that I am developing. You can ignore the calf type.

  • Now lets dive a little deeper into the SQL code and under stand what it is saying.

SELECT

  • Select determines which columns to include in our query set and then returns the rows that have those columns. Despite actually being the first query clause(the capital letters determine what is a query clause) it is actually one of the last clauses that the database server evaluates.

FROM

  • Identifies the tables from which to draw data. It also identifies how the tables should be joined but since we are only querying one table we don't need to worry about it.

WHERE

  • This clause is the mechanism for filtering out unwanted rows from our result set.
  • lastly the : is how we specify a parameter we defined ourselves.

  • So to put all this together and to translate the SQL query to human speak we get, show all the columns and rows from the calf_table, where the id is equal to the id we specified..

The Repository

  • Now that we understand our query, lets dive into our repository. If you are unfamiliar with what a repository is, it is just a architecture technique used to allow for loose coupling in databases.
public Calf getCalf(int calfId) throws ExecutionException, InterruptedException {
        Future<Calf> calf =  CalfRoomDatabase
                .databaseWriteExecutor.submit(new Callable<Calf>() {
            @Override
            public Calf call()  {
                return mCalfDao.getCalf(calfId);
            }
        });
        //GET SHOULD WAIT UNTIL WE GET WHAT WE WANT
        Calf gettingCalf = calf.get();
        return gettingCalf;
    }

Enter fullscreen mode Exit fullscreen mode
  • As you can see this method starts off very normal but you will notice throws ExecutionException, InterruptedException in the method declaration. Before we can understand what is going on here we need to understand exceptions as a whole.

What is an Exception

  • If you are ready know about exceptions feel free to skip this part.
  • An exception is actually short hand for exceptional event but we will stick to exception. An exception is an event which occurs during the execution of a program that disrupts the normal flow of said program.

What happens when an exception is encountered?

  • When an error occurs within a method, the method creates an object then hands it off to the runtime system. The object, called an exception, contains information about the error, including its type and the state of the program when the error occurred. Creating an exception object and handling it to the runtime system is called throwing an exception.

How an exception is handled

  • When an exception is thrown, the runtime system searches the callstack(data structure containing method calls ) for a method that contains a block of code that handles the exception. This block of code is called the exception handler. The search begins with the method in which the error occurred and proceeds through the callstack in the reverse order in which the methods were called. When the appropriate handler is found the runtime system passes the exception to the handler. An exception handler is considered appropriate if the type of the exception object thrown matches the type that can be handled by the handler. We will talk more about the exception handler when we get into the try catch blocks.

Types of exceptions

  • There are actually 3 types of exceptions:

1) Checked exceptions
2) Errors
3) Runtime exception

  • The exceptions in our code, ExecutionException and InterruptedException are both considered checked exceptions so we will only be talking about checked exceptions.

Checked exceptions

  • Exceptions that fall under this category are exceptions that a well written application should anticipate and recover from. When dealing with checked exceptions we must abide by the Catch or specify Requirements, which basically says that we must use a try catch block to deal with the exceptions.

ExecutionException : thrown when attempting to retrieve the result of a task that aborted by throwing an exception.

InterruptedException :thrown when a thread is waiting, sleeping, or otherwise occupied, and the thread is interrupted, either before or during the activity.

Throws

 public Calf getCalf(int calfId) throws ExecutionException, InterruptedException {
Enter fullscreen mode Exit fullscreen mode
  • Now that we have a better understanding on what an exception is, lets talk about the throws keyword. When throws is used in the method declaration like we are using it, it describes the types of exceptions the method might encounter. It is also used to tell us and the compiler that we are going to deal with the exceptions higher up and the callstack. Which means a method that calls this method is going to deal with the exceptions via a try catch block, which we will see later

  • So long story show throws is used to signify two things:

1) The types of exceptions the method is going to encounter
2) Another method is going to handle the exceptions

  • Now lets move on to the future objects.

Future Class:

Future<Calf> calf =  CalfRoomDatabase
                .databaseWriteExecutor.submit(new Callable<Calf>() {
            @Override
            public Calf call()  {
                return mCalfDao.getCalf(calfId);
            }
        });
        //GET SHOULD WAIT UNTIL WE GET WHAT WE WANT
        Calf gettingCalf = calf.get();
        return gettingCalf;

Enter fullscreen mode Exit fullscreen mode
  • Notice in the code above that we have defined the return type of calf to be Future, which is a Future object containing a Calf object.

What is a Future object?

  • A Future represents the result of an asynchronous computation and it provides methods to check if computation is complete, to wait for its completion and to retrieve the results of the computation.

databaseWriteExecutor

  • This is actually of type ExecutorService which we used in our database class to create a thread pool. The tutorial I used to do this is HERE
  • thread pool : consists of worker threads that are used to execute multiple tasks. tasks being asynchronous requests.

  • Then we call databaseWriteExecutor.submit() which is available to use through the ExecutorService interface. submit() accepts a callable object, which allows our asyncronous tasks to return a value. In our case the return type is Future<Calf>.

Callable

new Callable<Calf>() {
            @Override
            public Calf call()  {
                return mCalfDao.getCalf(calfId);
            }
        }
Enter fullscreen mode Exit fullscreen mode
  • So a Callable is very similar to a runnable, meaning that they are both intended to be executed by a thread. In our case we are using worker threads, which are basically just less memory intensive threads. The big difference between the two is that callable can return a value, if you try to use a runnable you will never be able to get a value. You will only get null.
@Override
            public Calf call()  {
                return mCalfDao.getCalf(calfId);
            }

Enter fullscreen mode Exit fullscreen mode
  • When we create a callable it will call its call() method and that method is what will return a Future object. Inside this method is where we make a call to our DAO. mCalfDao.getCalf(calfId);
Calf gettingCalf = calf.get();
        return gettingCalf;
Enter fullscreen mode Exit fullscreen mode
  • Lastly we call get() which will wait, if necessary for the computation to complete and to retrieve the results. This method is what we turns the Future object into our Calf object.

ViewModel

  • This method is inside of a new file that is used to hold the ViewModel. If you are unfamiliar with a ViewModel, you can read more about it HERE
public Calf getCalf(int calfId) throws ExecutionException, InterruptedException {

       Calf calf = mRepository.getCalf(calfId);
        return calf;

    }
Enter fullscreen mode Exit fullscreen mode
  • As you can see we have already talked about everything here. We use throws to say, handle the exceptions somewhere else. We simply use this method to call the method on the repository.

Our Fragment

  • This code is inside of a Fragment, which is just a java object that extends Fragment. It actually happens inside the onViewCreated method.
            try {
                Calf calf = calf = mCalfViewModel.getCalf(calfId);
               String tagNumber = calf.getTagNumber();
               String description = calf.getDetails();

               updateTagNumber.setText(tagNumber);
               updateTextDescription.setText(description);

            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
Enter fullscreen mode Exit fullscreen mode
  • The first thing to notice is the try and catch blocks. These blocks are necessary when dealing with checked exceptions like we are. Like I stated earlier, checked exceptions must abide by the catch or specify requirement. This requirement states that we must handle exceptions in try and catch blocks. We use the try block to enclose code that might throw an exception. Then we provide catch blocks that will each catch one type of exception. you can do whatever you want inside those catch blocks but for use right now we are just printing out the error.

  • Lastly that is it. Than is how we make a single asynchronous request to our room database.

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.

Top comments (0)