Now that you're in your level 3 Epicodus course, you've probably come to realize how much trial-and-error goes into coding. Sometimes code doesn't work on the first try. Sometimes code that once worked perfectly no longer functions after implementing new code. Bugs occur all the time, and that's simply a normal part of development.
In the next two lessons we'll explore two Android-specific approaches to debugging. First, we'll learn how to record helpful information into an area of Android Studio known as the logcat. Then, in the next lesson we'll learn how to add breakpoints to strategically pause our code and narrow down where bugs and errors are occurring. Let's get started!
When writing JavaScript in your Intro to Programming and JavaScript courses, you probably encountered console.log()
. It’s a method that allows us to write to the JavaScript console in the browser. This allows us to see what certain variables are defined as, or check whether methods are being called.
Android Studio has the capability to write log messages in a very similar manner. We can add log methods that will write data to Android Studio's logcat. The logcat displays system messages, and messages/information you manually record with Log
methods. It both displays messages in real time and also keeps a history so you may view older messages.
After placing Log
methods we can run our app and look view the logcat to see what data or information has been recorded.
However, unlike JavaScript's console.log()
, there are many different methods from Android's Log
class we can use to log information. Let's briefly cover what these different methods are, and when to use each:
Log.e()
The e
in Log.e()
stands for error. Use this when you know an error has occurred, and you're logging details about that error. Developers will commonly use this in a block of code meant to catch an error. The Log.e()
message can then print details about the error.
Log.w()
The w
in Log.w()
stands for warning. Use this when you suspect an issue may be occurring, but haven't yet received full-on error messages. Developers usually use this to proactively investigate unusual or unexpected behavior in an application.
Log.i()
The i
in Log.i()
stands for information. Use this to post useful information. For instance, maybe you want to double-check a method is being called successfully, you could print an informational message to the log reading something like "X method called!".
Log.d()
The d
in Log.d()
stands for debug. As you might imagine, you'll use this one for debugging purposes. You'll probably use this one most frequently out of all available Log
methods.
Log.v()
The v
in Log.v()
stands for verbose. Use this when you're implementing many, many different log statements as a debugging approach.
Log.wtf()
(No, we're not making this up).
The wtf
in Log.wtf()
is said to stand for "What a terrible failure". It's meant to record particularly awful issues that should never, ever happen, but are somehow occurring anyway. It's not used as commonly as the other Log
messages.
But why are there 6 different methods to log information anyway? Well, the logcat can contain a lot of data. By classifying the importance of the information you're logging using the methods depicted above, we can easily filter messages by their level of importance.
The list of Log
methods above is ordered by level of importance, also known as "log level".
Log.e()
is considered to be the highest importance and priority, because it logs information about errors that are currently occurring, and Log.v()
is considered the lowest importance and priority because it's meant for logging as much data as possible. (Log.wtf()
is actually a bit of an outlier, and isn’t used very often.)
So, if you filter to view Log.i()
messages, you'll see both messages recorded with the Log.i()
method and those recorded with the Log.w()
and Log.e()
methods, because they are of the same importance level or higher.
Additionally, if we filtered by Log.d()
, we would see any messages printed on the logcat from Log.d()
, Log.i()
, Log.w()
, and Log.e()
because they are all of Log.d()
's level of importance or higher.
Similarly, if we were to filter by Log.v()
, we would see all other other Log
method messages, since Log.v()
is the lowest importance level, and each of the other methods is of a higher importance.
Let's walk through the process of logging and viewing data in Android Studio. We'll add several Log
messages to our MyRestaurants application together.
First, let's locate the logcat in Android Studio, so we'll know where to look for our logged messages. In previous lessons you learned how to access the Terminal in Android Studio. In the same pane near the bottom of the window, select Android Monitor from the lower options bar.
Then, once you're in the Android Monitor, select the logcat tab in the upper left:
If you do not have an Android Monitor option present in the lower option bar as seen above, you can instead access the Android Monitor by clicking the "square" in the lower left-hand corner of the window. Then, select Android Monitor from the resulting menu:
Next, just like the console.log()
method used in JavaScript, we can place a Log
method wherever we'd like. Information will be logged when that area of code runs.
We'll use Log.v()
and Log.d()
methods in two separate spots. First, we'll use Log.v()
in the onItemClick()
method. As you begin typing Log.v(...
, you'll see Android Studio will offer two suggestions in its auto-complete pop-up:
As you can see, this method takes either 2 or 3 arguments:
String
called tag
. In terms of Log
messages, a tag is a quick string that provides context regarding where the log message is coming from: Generally the name of the activity from which information is being logged.String
called msg
. This is the main content of the log message. Throwable
, which represents an exception or error being thrown. If you're attempting to log information about an exception or error, you'd provide this exception or argument as a third argument here. Our MyRestaurants application shouldn't be throwing any exceptions or errors in its current state, so let's begin by only providing two arguments:
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
...
Toast.makeText(RestaurantsActivity.this, restaurant, Toast.LENGTH_LONG).show();
Log.v("RestaurantsActivity", "In the onItemClickListener!");
}
});
...
...
"RestaurantsActivity"
, because we're logging this information from within our RestaurantsActivity. "In the onItemClickListener!"
So, when this code is triggered (ie: a restaurant from our list is clicked), the message "In the onItemClickListener!"
should appear in our logcat. Putting a log message in this location is great to test and confirm whether a click listener is being triggered successfully.
In order to experiment filtering different types of Log
messages, let's include a second Log
method of a different importance level. This time, we'll use Log.d()
just below our Toast
:
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
...
Log.v("RestaurantsActivity", "In the onItemClickListener!");
}
});
...
mLocationTextView.setText("Here are all the restaurants near: " + location);
Log.d("RestaurantsActivity", "In the onCreate method!");
}
...
Again, we include two arguments. The first is a String
containing the location from which we're logging. The second is the message we'd like to print to the logcat.
Next, let's launch our application in the emulator. Because we placed our Log
methods in the onCreate()
and onItemClick()
methods of our RestaurantsActivity, we'll need to trigger those methods in order for the Log
methods to record data in our logcat.
We'll navigate to our RestaurantsActivity. As soon as we do this, the onCreate()
method will be triggered. Therefore, we should see our first message appear in the logcat:
Then, we can trigger our second log message by clicking any of the restaurants in our list:
Since the Log.v()
method is located in our onItemClick()
method, notice that it is triggered whenever an item is clicked, instead of just once. For instance, if we clicked a restaurant many times in a row, we would see:
This should be fairly reminiscent of console.log()
in JavaScript. However, as you can see, there's a lot of information in that logcat! Thankfully, we can quickly filter this breadth of information.
In the top bar of the logcat panel, there are multiple options to filter logcat contents. Let's explore these options now.
This dropdown allows you to select which device's log messages to view. This should be the device you're currently running your application on. This will be the emulator, unless you're running your application on an standalone Android device.
This dropdown allows you to select which application's log messages to view. This should always be the application you're currently debugging, or attempting to view log messages for.
Here, you can filter by the log level. As we discussed previously, each of the Log
methods listed above is of a different priority level. When you filter by a specific log level, you'll only see messages of that priority level or higher.
For instance, if we filter by verbose, we can see both of our log statements are visible:
Yet, if we filter instead by debug, we can see that only our Log.d()
statement is visible, because we're viewing only log statements of the "Debug" priority or higher. Because verbose (Log.v()
) is of a priority lower than debug (Log.d()
), we no longer see it in the logcat:
As the name implies, the search field allows us to search for particular words, statement, or other content in a log message. For instance, if you logged a certain piece of data with our "RestaurantsActivity"
tag, but couldn't locate the log message, you could search "RestaurantsActivity"
to confirm whether or not the Log
method ever ran and recorded data in the logcat.
This dropdown allows us to choose whether we'd like to see log messages from only the selected application (the application chosen in #2), or from all currently-running applications and processes on the device.
Additionally, it's common practice for Android Developers to add a special constant called TAG
that containis the name of the activity to each class. This constant is then used as the first argument in any Log
methods.
Defining a TAG
constant for use in an activity's Log
methods looks like this:
public static final String TAG = YourActivityName.class.getSimpleName();
By defining the constant as YourActivityName.class.getSimpleName();
, instead of a String
containing the activity name, Android Studio's built-in refactoring tools will automatically change the value of this constant if you ever re-name your activity. This is considered best practice.
Let's add a similar constant to our RestaurantActivity, and use it in both our Log
methods:
...
public static final String TAG = RestaurantActivity.class.getSimpleName();
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
...
Toast.makeText(RestaurantsActivity.this, restaurant, Toast.LENGTH_LONG).show();
Log.v(TAG, "In the onItemClickListener!");
}
});
...
mLocationTextView.setText("Here are all the restaurants near: " + location);
Log.d(TAG, "In the onCreate method!");
...
...
As you saw earlier, Log
methods take 2-3 arguments. Most of the time, you'll only ever need to use 2: A tag containing information about the context the log message is coming from, and the contents of the message. Both of these arguments must be String
type.
This means, you can only log String
information. At first this may sound limiting, but note that you can simply use the .toString()
method to turn non-String
data into String
format for the purposes of logging.
For more information on debugging in general, checkout the Debug Your App portion of the Android Studio User Guide.
For more information on Android Studio's logcat, check out the logcat Command-line Tool article.
Logcat: An area of Android Studio that displays system messages, and messages/information you manually record with Log
methods. It displays messages in real time and also keeps a history so you may view older messages.
Tag: A string that provides context regarding where the log message is coming from: Generally the name of the activity from which information is being logged.
Log.d()
: Debug. You'll use this one for debugging purposes. You'll probably use this one most frequently out of all available Log
methods.
Log.e()
: Error. Use this when you know an error has occurred, and you're logging details about that error.
Log.i()
: Information. Use this to post useful information. For instance, maybe you want to double-check a method is being called successfully, you could print an informational message to the log reading something like "X method called!".
Log.v()
: Verbose. Use this when you're implementing many, many different log statements as a debugging approach.
Log.w()
: Warning. Use this when you suspect an issue may be occurring, but haven't yet received full-on error messages.
Log.wtf()
: "What a terrible failure". Meant to record particularly awful issues that should never, ever happen, but are somehow occurring anyway.