Intro to debugging

1. Before you begin

Anyone who's used a piece of software has most likely encountered a bug. A bug is an error in a piece of software that causes unintended behavior, such as an app crashing, or a feature not working as expected. All developers, regardless of experience, introduce bugs when they write code, and one of the most important skills for an Android developer is to identify and fix them. It's not uncommon to see entire app releases dedicated to fixing bugs. For example, see the version details of Google Maps below:

9d5ec1958683e173.png

The process of fixing bugs is called debugging. The famous computer scientist Brian Kernighan once said that "the most effective debugging tool is still careful thought, coupled with judiciously placed print statements." While you may already be familiar with Kotlin's println() statement from previous codelabs, professional Android developers use logging to better organize their program's output. In this codelab, you'll learn how to use logging in Android Studio, and how it can be used as a debugging tool. You'll also learn to read error message logs, called stack traces, to identify and solve bugs. Finally, you'll learn how to research bugs on your own, as well as learn how you can capture output from the Android emulator, as either a screenshot or a GIF of your running app.

Prerequisites

  • You know how to navigate a project in Android Studio.

What you'll learn

By the end of this codelab, you'll be able to

  • Write logs using android.util.Logger.
  • Know when to use different log levels.
  • Use logs as a simple and powerful debugging tool.
  • How to find meaningful information in a stack trace.
  • Search for error messages to solve application crashes.
  • Capture screenshots and animated GIFs from the Android Emulator.

What you'll need

  • A computer with Android studio installed.

2. Create a new project

Rather than use a large and complex app, we're going to start with a blank project to demonstrate log statements and their usage for debugging.

Start by creating a new Android Studio project, as shown.

  1. On the New Project screen, choose Empty Activity.

72a0bbf2012bcb7d.png

  1. Name the app Debugging. Make sure the language is set to Kotlin, and leave everything else unchanged.

60a1619c07fae8f5.png

After creating the project, you'll be greeted with a new Android Studio project, showing a file called MainActivity.kt.

e3ab4a557c50b9b0.png

3. Logging and debug output

In the previous lessons, you used Kotlin's println() statement to produce text output. In an Android app, the best practice for logging output is using Log class. There are several functions for logging output, taking the form Log.v(), Log.d(), Log.i(), Log.w(), or Log.e(). These methods take two parameters: the first, called the "tag", is a string identifying the source of the log message (such as the name of the class that logged the text). The second is the actual log message.

Perform the following steps to start using logging in your blank project.

  1. In MainActivity.kt, before the class declaration, add a constant called TAG, and set its value to the name of the class, MainActivity.
private const val TAG = "MainActivity"
  1. Add a new function to the MainActivity class called logging() as shown.
fun logging() {
    Log.v(TAG, "Hello, world!")
}
  1. Call logging() in onCreate(). The new onCreate() method should look like the following.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    logging()
}
  1. Run the app to see the logs in action. Logs appear in the Logcat window at the bottom of the screen. Because Logcat will show output from other processes on the device (or emulator), you can select your app (com.example.debugging) from the dropdown menu to filter out any logs that aren't relevant to your app.

199c65d11ee52b5c.png

In the output window, you should be able to see your "Hello, world!" output. If needed, type "hello" in the search box at the top of the Logcat window, to search all the logs.

92f258013bc15d12.png

Log levels

The reason different log functions exist, named with different letters, is because they correspond to different log levels. Depending on what type of information you want to output, you would use a different log level to help you filter it in the Logcat output. There are five main log levels that you'll use regularly.

Log level

Use case

Example

ERROR

ERROR logs report that something went seriously wrong, such as the reason why an app crashed.

Log.e(TAG, "The cake was left in the oven for too long and burned.").

WARN

WARN logs are less severe than an error but still report something that should be fixed to avoid a more serious error. An example might be if you call a function that's deprecated, meaning that its use is discouraged in favor of a newer alternative.

Log.w(TAG, "This oven does not heat evenly. You may want to turn the cake around halfway through to promote even browning.")

INFO

INFO logs provide useful information, such as an operation being successfully completed.

Log.i(TAG, "The cake is ready to be served.").println("The cake has cooled.")

DEBUG

DEBUG logs contain information that may be useful when investigating an issue. These logs are not present in release builds such as one you'd publish on the Google Play Store.

Log.d(TAG, "Cake was removed from the oven after 55 minutes. Recipe calls for the cake to be removed after 50 - 60 minutes.")

VERBOSE

As the name implies, verbose is the least specific log level. What's considered a debug log, versus a verbose log, is a bit subjective, but generally, a verbose log is something that can be removed after a feature is implemented, whereas a debug log may still be useful for debugging. These logs are also not included in release builds.

Log.v(TAG, "Put the mixing bowl on the counter.")Log.v(TAG, "Grabbed the eggs from the refrigerator.")Log.v(TAG, "Plugged in the stand mixer.")

Keep in mind that there are no set rules for when to use each type of log level, particularly for when to use DEBUG and VERBOSE. Software development teams may create their own guidelines for when to use each log level, or perhaps decide not to use certain log levels, like VERBOSE, at all. The important thing to remember about these two log levels is that they're not present in release builds, so using logs to debug won't impact the performance of published apps, whereas println() statements remain in release builds and do negatively impact performance.

Let's see what these different log levels look like in Logcat.

  1. In MainActivity.kt, replace the contents of the logging() method with the following.
fun logging() {
    Log.e(TAG, "ERROR: a serious error like an app crash")
    Log.w(TAG, "WARN: warns about the potential for serious errors")
    Log.i(TAG, "INFO: reporting technical information, such as an operation succeeding")
    Log.d(TAG, "DEBUG: reporting technical information useful for debugging")
    Log.v(TAG, "VERBOSE: more verbose than DEBUG logs")
}
  1. Run your app and observe the output in Logcat. If necessary, filter the output to only show the logs from the com.example.debugging process. You can also filter the output to only show logs with the "MainActivity" tag. To do this, select Edit Filter Configuration from the dropdown menu at the top right of the Logcat window.

383ec6d746bb72b1.png

  1. Then type "MainActivity" for the Log Tag, and create a name for your filter as shown.

e7ccfbb26795b3fc.png

  1. Now you should only see log messages with the "MainActivity" tag.

4061ca006b1d278c.png

Notice how there's a letter before the class name, for example, W/MainActivity, corresponding to the log level. Additionally, the WARN log is shown in blue whereas the ERROR log is shown in red, just like the fatal error in the previous example.

  1. Just as you can filter the debug output by process, you can also filter the output by log level. By default, this is set to Verbose, which will show VERBOSE logs and higher log levels. Select Warn from the dropdown menu and note that now only WARN and ERROR level logs are shown.

c4aa479a8dd9d4ca.png

  1. Again, change the dropdown to Assert, and observe that no logs are shown. This filters out everything that's ERROR level and below.

ee3be7cfaa0d8bd1.png

While this may seem like taking println() statements a bit too seriously, as you build larger apps, there will be a lot more Logcat output, and using different log levels will let you pick out the most useful and relevant information. Using Log is considered a best practice and is preferred over println() in Android development, as debug and verbose logs won't impact performance in release builds. You can also filter logs based on different log levels. Choosing the correct log level will benefit others on your development team who may not be as familiar with the code as you are, and makes it much easier to identify and solve bugs.

4. Logs with error messages

Introduce a bug

There's not much debugging to do in a blank project. Many of the bugs you'll encounter as an Android developer involve apps crashing - obviously not a good user experience. Let's add some code that causes this app to crash.

You may remember having learned in math class that you can't divide a number by zero. Let's see what happens when we try to divide by zero in code.

  1. Add the following function to your MainActivity.kt above the logging() function. This code starts with two numbers and uses repeat to log the result of dividing the numerator by the denominator five times. Each time the code in the repeat block runs, the value of the denominator is decreased by one. On the fifth and final iteration, the app attempts to divide by zero.
fun division() {
    val numerator = 60
    var denominator = 4
    repeat(5) {
        Log.v(TAG, "${numerator / denominator}")
        denominator--
    }
}
  1. After the call to logging() in onCreate(), add a call to the division() function.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    logging()
    division()
}
  1. Run your app again and note that it crashes. If you scroll down to the logs from your MainActivity.kt class, you'll see the ones from the logging() function you defined before, the verbose logs from the division() function, and then a red error log explaining why the app crashed.

12d87f287661a66.png

Anatomy of a stack trace

The error log describing the crash (also called an exception) is called a stack trace. The stack trace shows all the functions that were called leading up to the exception, starting with the most recently called. The full output is shown below.

Process: com.example.debugging, PID: 14581
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.debugging/com.example.debugging.MainActivity}: java.lang.ArithmeticException: divide by zero
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.debugging.MainActivity.division(MainActivity.kt:21)
        at com.example.debugging.MainActivity.onCreate(MainActivity.kt:14)
        at android.app.Activity.performCreate(Activity.java:8000)
        at android.app.Activity.performCreate(Activity.java:7984)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7656) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 

That's a lot of text! Thankfully, you typically only need a few pieces to narrow down the exact error. Let's start at the top.

  1. java.lang.RuntimeException:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.debugging/com.example.debugging.MainActivity}: java.lang.ArithmeticException: divide by zero

The first line states that the app was unable to start the activity, which is the reason for the app crashing. The next line provides a bit more information. Specifically, the reason the activity couldn't start was because of an ArithmeticException. More specifically, the type of ArithmeticException was "divide by zero".

  1. Caused by:
Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.debugging.MainActivity.division(MainActivity.kt:21)

If you scroll down to the "Caused by" line, it again says there was a "divide by zero" error. This time it also shows you the exact function where the error occurred (division()), and the exact line number (21). The file name and line number in the Logcat window are hyperlinked. The output also shows the name of the function where the error occurred, division(), and the function that called it, onCreate().

None of this should come as a surprise, since the bug was deliberately introduced. However, if you need to determine the cause of an unknown error, knowing the exact type of exception, function name, and line number provide incredibly useful information.

Why a "stack trace"?

The term "stack trace" might sound like a strange term for the text output from an error. To better understand how this works, you need to know a little more about the function stack.

When one function calls another function, the device won't run any code from the first function until the second function finishes. Once the second function finishes executing, the first function resumes where it left off. The same goes for any functions called by the second function. The second function won't resume executing until the third function (and any other functions it calls) finish, and the first function won't resume until the second function finishes executing. This is similar to a stack in the physical world, such as a stack of plates or a stack of cards. If you want to take a plate, you're going to take the top most one. It's impossible to take a plate lower in the stack without first removing all the plates above it.

The function stack can be illustrated with the following code.

val TAG = ...

fun first() {
    second()
    Log.v(TAG, "1")
}

fun second() {
    third()
    Log.v(TAG, "2")
    fourth()
}

fun third() {
    Log.v(TAG, "3")
}

fun fourth() {
    Log.v(TAG, "4")
}

If you call first(), then the numbers will be logged in the following order.

3
2
4
1

Why is this? When the first function is called, it immediately calls second(), so the number 1 can't be logged right away. The function stack looks like this.

second()
first()

The second function then calls third(), which adds it to the function stack.

third()
second()
first()

The third function then prints the number 3. Once it finishes executing, it is removed from the function stack.

second()
first()

The second() function then logs the number 2, and then calls fourth(). So far, the numbers 3, and then 2, have been logged and the function stack is now as follows.

fourth()
second()
first()

The fourth() function prints the number 4, and is removed (popped) off of the function stack. The second() function then finishes executing and is popped off the function stack. Now that second() and all functions it called have finished, the device then executes the remaining code in first() which prints the number 1.

Thus, the numbers are logged in the order: 4, 2, 3, 1.

By taking your time and walking through the code, and keeping a mental image of the function stack, you can see exactly what code is executed and in what order. This alone can be a powerful debugging technique for bugs like the division by zero example above. Stepping through the code can also give you a good idea of where to put log statements to help debug more complex problems.

5. Using logs to identify and fix the bug

In the previous section you examined the stack trace, specifically this line.

Caused by: java.lang.ArithmeticException: divide by zero
        at com.example.debugging.MainActivity.division(MainActivity.kt:21)

Here, you can tell that the crash occurred on line 21 and was related to dividing by zero. Thus, somewhere before this code executes, the denominator was 0. While you could try to step through the code on your own, which could work perfectly well for a small example like this, you can also use log statements to save time by printing the value of the denominator before the divide by zero occurs.

  1. Before the Log.v(), statement, add a Log.d() call that logs the denominator. Log.d() is used because this log is specifically for debugging, and so that you can filter out the verbose logs.
Log.d(TAG, "$denominator")
  1. Run your app again. Although it still crashes, the denominator should be logged several times. You can use a Filter Configuration to only show logs with the "MainActivity" tag.

d6ae5224469d3fd4.png

  1. You can see that multiple values are printed. It appears the loop executes a few times before crashing on the fifth iteration when the denominator is 0. This makes sense, because the denominator is 4, and the loop decrements it by 1 for 5 iterations. To fix the bug, you can change the number of iterations in the loop from 5 to 4. If you re-run the app, it should no longer crash.
fun division() {
    val numerator = 60
    var denominator = 4
    repeat(4) {
        Log.v(TAG, "${numerator / denominator}")
        denominator--
    }
}

6. Debugging example: accessing a value that doesn't exist

By default, the Blank Activity template you used to create the project adds a single activity, with a TextView centered on the screen. As you learned previously, you can reference views from code by setting an ID in the layout editor and accessing the view with findViewById(). When onCreate() is called in an activity class, it's first necessary to call setContentView() to load a layout file (such as activity_main.xml). If you try to call findViewById() before calling setContentView(), the app will crash because the view does not exist. Let's try to access the view to help illustrate another bug.

  1. Open activity_main.xml, select the Hello, world! TextView, and set the id to hello_world.

c94be640d0e03e1d.png

  1. Back in ActivityMain.kt in onCreate(), add code to get the TextView and change its text to "Hello, debugging!" before the call to setContentView().
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val helloTextView: TextView = findViewById(R.id.hello_world)
    helloTextView.text = "Hello, debugging!"
    setContentView(R.layout.activity_main)
    division()
}
  1. Run the app again, and note that it again crashes immediately upon launch. You may need to remove the filter from the previous example to see logs without the "MainActivity" tag. 840ddd002e92ee46.png

The exception should be one of the last things to appear in Logcat (if not, you can search for RuntimeException). The output should look like the following.

Process: com.example.debugging, PID: 14896
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.debugging/com.example.debugging.MainActivity}: java.lang.NullPointerException: findViewById(R.id.hello_world) must not be null
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.NullPointerException: findViewById(R.id.hello_world) must not be null
        at com.example.debugging.MainActivity.onCreate(MainActivity.kt:14)
        at android.app.Activity.performCreate(Activity.java:8000)
        at android.app.Activity.performCreate(Activity.java:7984)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7656) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 

Like before, at the top it says "Unable to start activity". This makes sense as the app crashed before MainActivity even launched. The next line tells a bit more about the error.

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.debugging/com.example.debugging.MainActivity}: java.lang.NullPointerException: findViewById(R.id.hello_world) must not be null

Further down in the stack trace, you'll also see this line, showing the exact function call and line number.

Caused by: java.lang.NullPointerException: findViewById(R.id.hello_world) must not be null

What exactly does this error mean, and what exactly is a "null" value? While this is a contrived example and you may already have an idea as to why the app crashed, you'll inevitably encounter error messages you haven't seen before. When this happens, chances are you're not the first one to see the error, and even the most experienced developers will often Google the error message to see how others solved the problem. Looking up this error yields several results from StackOverflow, a site where developers can ask questions and give answers about buggy code or more general programming topics.

As there may be many questions with similar but not exactly the same answers, keep the following tips in mind when searching for answers on your own.

  1. How old is the reply? Replies from several years ago may no longer be relevant, or could be using an outdated version of a language or framework.
  2. Is the answer using Java or Kotlin? Is your issue specific to one language or the other, or related to a specific framework?
  3. Answers marked as "accepted" or with more upvotes might be higher quality, but keep in mind that other answers can still provide valuable information.

1636a21ff125a74c.png

A number indicates the number of upvotes (or downvotes) and a green checkmark indicates an accepted answer.

If you can't find an existing question, you can always ask a new question. When asking a question on StackOverflow (or any site), it's a good idea to keep in mind these guidelines.

Go ahead and search for the error.

a60ba40e5247455e.png

If you read through some of the answers, you'll find that the error can have multiple different causes. However, given that you deliberately called findViewById() before setContentView(), some of the answers on this question sound promising. The second most voted answer, for example, says:

"Possibly, you are calling findViewById before calling setContentView? If that's the case, try calling findViewById AFTER calling setContentView"

After seeing this answer, you can then verify in the code that indeed, the call to findViewById() is too early, coming before setContentView(), and that it should instead be called after setContentView().

Fix the error by updating the code.

  1. Move the call to findViewById() and the line that sets the helloTextView's text below the call to setContentView(). The new onCreate() method should look as shown below. You can also add logs, as shown, to verify that the bug is fixed.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    Log.d(TAG, "this is where the app crashed before")
    val helloTextView: TextView = findViewById(R.id.hello_world)
    Log.d(TAG, "this should be logged if the bug is fixed")
    helloTextView.text = "Hello, debugging!"
    logging()
    division()
}
  1. Rerun the app. Note that the app no longer crashes, and the text is updated as expected.

9ff26c7deaa4a7cc.png

Take screenshots

By now, you've probably seen numerous screenshots from the Android emulator in this course. Taking screenshots is relatively straightforward, but can be useful for sharing information, such as steps to reproduce bugs with other team members. You can take a screenshot in the Android emulator by pressing the camera icon in the toolbar on the right.

455336f50c5c3c7f.png

You can also use the keyboard shortcut Command+S to take a screenshot. The screenshot is automatically saved to your Desktop folder.

Record a running app

While screenshots can convey a lot of information, sometimes it's helpful to share recordings of an app running to help others reproduce something that caused a bug. The Android emulator offers some built-in tools to help you easily capture a GIF (animated image) of the running app.

  1. In the emulator tools on the right, click the More 558dbea4f70514a8.png button (last option) to show the additional emulator debugging options. A window pops up that provides additional tools for simulating the functionality of physical devices for testing purposes.

46b1743301a2d12.png

  1. On the left side menu, click Record and Playback and you'll see a screen with a button to start recording.

dd8b5019702ead03.png

  1. Right now, your project doesn't have anything that interesting to record, other than a static TextView. Let's modify the code to update the label every few seconds to show the result of the division. In the division() method in MainActivity, add a call to Thread.sleep(3000) before the call to Log(). The method should now look as follows (note that the loop should only repeat 4 times to avoid a crash).
fun division() {
   val numerator = 60
   var denominator = 4
   repeat(4) {
       Thread.sleep(3000)
       Log.v(TAG, "${numerator / denominator}")
       denominator--
   }
}
  1. In activity_main.xml, set the id of the TextView to division_textview.

db3c1ef675872faf.png

  1. Back in MainActivity.kt, replace the call to Log.v() with the following calls to findViewById() and setText() to set the text to the quotient.
findViewById<TextView>(R.id.division_textview).setText("${numerator / denominator}")
  1. Because you are now rendering the division's result in the app UI, you need to take care of some details on how your UI updates run. First, you need to create a new thread that can run the repeat loop. Otherwise, Thread.sleep(3000) would block the main thread, and the app view would not render until onCreate() is finished (including division() with its repeat loop).
fun division() {
   val numerator = 60
   var denominator = 4

   thread(start = true) {
      repeat(4) {
         Thread.sleep(3000)
         findViewById<TextView>(R.id.division_textview).setText("${numerator / denominator}")
         denominator--
      }
   }
}
  1. If you try running the app now, you notice a FATAL EXCEPTION. The reason for this exception is that only threads that created a view are allowed to change it. Luckily, you can reference the UI thread using runOnUiThread(). Change division() to update the TextView within the UI thread.
private fun division() {
   val numerator = 60
   var denominator = 4
   thread(start = true) {
      repeat(4) {
         Thread.sleep(3000)
         runOnUiThread {
            findViewById<TextView>(R.id.division_textview).setText("${numerator / denominator}")
            denominator--
         }
      }
   }
}
  1. Run your app and then immediately switch to the emulator. When the app launches, click the Start Recording button in the Extended Controls window. You should see the quotient be updated every three seconds. After the quotient has been updated a few times, click Stop Recording.

55121bab5b5afaa6.png

  1. By default, the output is saved in .webm format. Use the dropdown to export the output as a GIF file.

850713aa27145908.png

7. Congratulations

Congratulations! In this pathway you learned that:

  • Debugging is the process of troubleshooting the bugs in your code.
  • Log allows you to print text with different log levels and tags.
  • The stack trace provides information about an exception, such as the exact function that caused it and the line number where the exception occurred.
  • When debugging, chances are someone has encountered the same or a similar problem, and you can use sites like StackOverflow to research the bug.
  • You can easily export both screenshots and animated GIFs using the Android emulator.

Learn more