5.
Watches & Evaluating the Code
Written by Vincenzo Guzzi
A major part of debugging is interacting with and changing variables when your code is in a suspended state. This allows you to manipulate responses to simulate different scenarios and see how your app will react. The Variables pane inside the Debug window allows you to perform this manipulation of variables by providing useful tools, which you’ll be exploring in this chapter.
You’ll learn how to:
- Read and use the Variables pane.
- Make important variables more visible by using flagged and the Watched pane.
- Evaluate code at runtime.
- Change the values of variables to find bugs.
- Augment code with Force Return and Throw Exception.
Exploring The Variables Pane
The Variables pane, like everything else in the Debug window, is only viewable when your code is suspended. This pane contains all of the information about the variables that your current context can view. Within this pane, you can also manipulate the data by setting values, finding usages, or pinning important information.
To start, open the Podplay starter project in Android Studio. Open EpisodeListAdapter.kt, and add a breakpoint on the first line inside onBindViewHolder() so you can suspend your code inside this method.
Run the app in debug mode. Once PodPlay has loaded, tap the search icon, enter the term “All about android” and open the All About Android (Audio) podcast.
Your program is now suspended inside onBindViewHolder() where you placed your breakpoint. The Debug window automatically opens when your code suspends. If it doesn’t, open it manually by clicking View ▸ Tool Windows ▸ Debug.
You’ll locate the Variables pane to the right of the Debug window:
Each line inside the Variables pane is a variable; these can be primitives or objects. The first variable you see is this.
In Android, this refers to the scoped context. In this case, it’s the context of EpisodeListAdapter. The Variables pane will always show the scoped context as the first variable in the list. That way you can find out what context is accessible from wherever your code is suspended.
There’s a chevron located to the left of this, which means that it’s an object containing additional values. Click the chevron to expand this and see its values.
Each variable in the pane provides the variable’s name and its value. If it’s an object, it will display metadata to help you understand what type of object it is.
In the second row of this is a variable called episodeViewList. This is what the row shows:
- A chevron that you can click to expand the object and see the variables inside.
- The variable identifier — this one indicates that
episodeViewListis a final field. - The variable name.
- The variable type.
- The object ID.
- The variable value.
In this case, the local scoped context (this) will always appear in the first row of the Variables pane. The following variables in the list will be the ones that are either method parameters or ones created within the current scope.
The following two variables in the list are holder and position. Both of them have been passed into onBindViewHolder() as parameters.
Click Step over twice to execute the next two lines of code.
You’ll now see that two new variables have appeared in the Variables pane; episodeViewList and episodeView. These local variables have just been created in the two lines you just executed.
Drag and drop your breakpoint to your new line, so future suspends will occur on this line:
Pinning and Watching Variables
The Variables pane is often full of information. The pane itself doesn’t know what variables are most important to you. However, it does provide a way for you to let it know.
The first and easiest way to make variables that you care about more visible is to pin them to the top of an object. For now, Android Studio only allows you to pin variables that are within an object and not in the top level of the Variables pane.
Do this for the String variable title. Find it within episodeView by clicking the chevron to expand episodeView. Then scroll down and tap the variable type identifier of title. It will turn into a flag icon when your mouse hovers over it.
The first item within episodeView will now be title and it will stay there in subsequent debugging sessions.
You’ve now also changed the variable type icon to a blue flag to indicate that it has been pinned. To unpin a variable, simply click the flag icon again, and it will go back to its ordered position.
For super important variables that you want to be able to monitor all the time, you can add a variable to your Watches pane.
To show your Watches pane, click the glasses icon to the left of the Variables pane:
Add title to your Watches by selecting the + icon inside the Watches pane. Type in episodeView.title and then press Return:
episodeView.title will now be permanently shown inside your Watches pane. Click Resume Program twice to see your watched variable update each time.
Use the + icon to add another watch for episodeView.duration:
Simply right-click a watched variable and select Remove Watch to remove a watch.
Evaluating Code
Whenever your code is in a suspended state, you have access to retrieve and manipulate everything available to your current scope. The most instantaneous way to manipulate your code is with Evaluate Expression.
You’ll find Evaluate Expression at the end of the debugging actions toolbar:
Click Evaluate Expression to open up the Evaluate window:
Here, you can enter any code you like, and if your scoped suspended position can evaluate the code, you can do that here too.
You can use the Evaluate button when debugging for many use cases; see below for a few examples that you can try yourself in the current scope:
-
Checking over data content by running:
episodeView.description.contains("Android") -
Seeing if a primitive type is greater than a certain value:
position > 10 -
Running logic that hasn’t yet been executed:
DateUtils.dateToShortDate(episodeView.releaseDate!!)
If you want to see all of your previously evaluated expressions, use the small arrow to the right of the Expression window.
The ability to evaluate code is a powerful tool when debugging. As well as debugging your existing logic and data, you can use evaluating code to test logic that you haven’t yet coded so. Then you can see if it works as intended before committing it to a real method. As your code doesn’t have to be re-compiled to test different logic, the speed at which you can try different code saves a lot of time!
Augmenting Code
Code augmentation is the act of changing the value of a variable to something different from its real value. You can use this to simulate different data responses and evaluate how your app UI responds.
There are a few different tools within Android Studio that you can utilize to augment code. The easiest way to do this is to directly change the value of a variable when your program is suspended.
If your program is running, stop it by clicking the Stop icon.
Right-click the breakpoint inside onBindViewHolder() and click More to expand the Breakpoints window.
Check the Condition box and enter the condition of:
position == 1
Click Done.
Your breakpoint will now only suspend the first time onBindViewHolder() is called; this is when you’ll augment your code.
Build and run your app again in debug mode. Search for and click All About Android (Audio) again, so your app suspends on your breakpoint.
You’re going to debug how your program behaves when there’s an unexpected empty String in the variable mediaUrl.
In the Variables pane, expand episodeViewList:
You’re now viewing all of the podcast episodes in an ArrayList. Expand the first item by clicking the chevron next to 0:
This is the podcast episode that you’re going to augment. Scroll down until you see the variable mediaUrl, now right-click the variable and select Set Value…:
Replace the mediaUrl value with an empty text and press Return:
Now, click the Resume Program icon so that you can see how your program responds.
With PodPlay running, select the first podcast episode in the list, the one that you augmented.
Now try and play the podcast episode by clicking the Play icon.
Ouch! Your app crashed.
Open the Logcat window, and you’ll see an IllegalArgumentException stating: “You must specify a non-empty Uri for playFromUri.”
This is bad as mediaUrl is a variable that an external API sends. You can’t expect it always to be present. Your logic needs to handle edge cases where data is empty like this.
Click the crash link for togglePlayPause to go to the method that threw the IllegalArgumentException.
You can use Evaluate expression to quickly test a fix for this crash.
Remove the previous breakpoint and place a new one on the first line inside togglePlayPause().
Now build and run your app again in debug mode and go through all of the previous debugging steps:
- Search for “Android”.
- Open the All About Android (Audio) podcast.
- Change the value of
mediaUrlwithin the first element ofepisodeViewListto an emptyString. - Resume your program by clicking the Resume icon.
- Tap on the first podcast episode on the list.
- Play the podcast episode by tapping the Play icon.
Your app should now be suspended inside togglePlayPause() right before the crash occurs.
Click Evaluate Expression so you can see which code to use to capture this scenario.
Try to find the correct code to use for checking if the mediaUrl text is empty by using Evaluate Expression.
Did you find the answer?
If not, here is the winning logic:
podcastViewModel.activeEpisodeViewData?.mediaUrl?.isEmpty()
You just saved yourself a lot of time running through the debug steps multiple times to test different pieces of logic until you found the correct one.
Now you can add the isEmpty check at the first line of togglePlayPause():
if (podcastViewModel.activeEpisodeViewData?.mediaUrl?.isEmpty() == true) {
Log.d("test", "MediaURL is empty, unable to play podcast episode.")
Toast.makeText(context, "Unable to play podcast episode, media URL missing.", Toast.LENGTH_SHORT)
return
}
Make sure you add the Log and Toast imports to the top of EpisodePlayerFragment.kt:
import android.util.Log
import android.widget.Toast
This code will now check if the mediaUrl is empty. If it’s empty, it’ll display a toast to the user and return from the method instead of crashing.
Go through the previous debug steps again to try out your new fix if you’d like!
Force Return
Changing the values of variables during code suspension is a powerful debugging tool, but what if you wanted to augment the returning value of a method? You can do this with Force Return.
Force Return allows you to end your current scope and return an evaluated expression to the scope above it. Like direct code augmentation, it’s a great way to test different scenarios of what a method might return.
To demonstrate Force Return, remove all of your existing breakpoints by selecting Run ▸ View Breakpoints… and deleting them.
Then, add a breakpoint onto the first line inside podcastToPodcastView() located in PodcastViewModel.kt:
Next, you’ll see how your UI behaves if a podcast doesn’t have a title or description in this scenario.
Build and run your app again in debug mode, search for a podcast and select the first one. Force Return lives in the Frames pane that you explored in a previous chapter. In the Frames pane, you can see that the currently selected frame is the method that you’re suspended in;podcastToPodcastView():
Right-click your frame and select the option Force Return:
A new window will now be visible called Return Value with an expression input box. This is the place where you’ll return a new, augmented data value.
Select the expand icon next to the expression box, so you have more visibility of the code you’re inputting:
Now, return a PodcastViewData object that has an empty title and description:
PodcastViewData(podcast.id != null, "", podcast.feedUrl, "", podcast.imageUrl, episodesToEpisodesView(podcast.episodes))
This was copied from the existing method logic within podcastToPodcastView() with the title and description parameters replaced.
Click the collapse icon and then click OK.
Note: Android Studio 2021.2.1 has an issue when pressing OK; the Return Value window doesn’t close automatically. Clicking OK again will result in an error that states “Error while doing early return: Thread has been resumed.” To overcome this issue, press Cancel after pressing OK the first time.
Your frame will now have been returned, but your app is still suspended; resume your app by clicking the Resume Program icon.
You have now successfully augmented your code with Force Return, you’ll now see your podcast without a title and description within your UI. You’re also free to leave it as it is or change how your app behaves when this data is missing.
Throwing Exceptions
You can also augment code by throwing exceptions. Throwing rogue exceptions at your program is a great way to see how resilient your code is.
Remove your existing breakpoint in PodcastViewModel. Then open PodplayMediaCallback.kt and scroll down to setState().
Within setState() you’ll see that halfway through the method, there’s a try-catch statement that wraps around this code:
mediaPlayer.playbackParams = mediaPlayer.playbackParams.setSpeed(speed)
If setSpeed() throws an exception, a set of logic is run. The only trouble is that it’s hard to verify that the exception logic works if setSpeed() always works. This is where you can augment the code to throw an exception.
Remove the last added breakpoint and set a new breakpoint on the line from the previous code block:
Now, build and run PodPlay in debug mode.
Suspend the code on this breakpoint by searching for a podcast, selecting an episode, and then playing that podcast episode.
Your code is now suspended on setSpeed(). Normally, this method would execute, and your catch statement wouldn’t be hit. For this case, right-click your frame inside the Frames pane and select Throw Exception.
You’ll see the new Exception To Throw window:
Enter the exception that you’ll force return:
RuntimeException("Boom!")
Click OK.
Your code has now landed within the catch statement!
Click Step over once to move down into your catch scope, and you’ll see the exception that you just created, with the message “Boom!” has been caught.
Click Resume Program, and your code will suspend again on the setSpeed() method, which is the correct functionality in this instance, you can rest assured knowing that your catch statement is behaving correctly.
Key Points
- Use the Variables pane to observe your local and context-accessed variables.
- Use Variable pinning and the Watches pane to keep important variables in view.
- You can use Evaluate Expression to debug your code logic without re-running your app.
- Use Force Return and Throw Exception to debug how your app behaves in unusual scenarios.
Where to Go From Here?
You’ve learned how to observe and change variables within Android Studio to create better, more resilient code, and you’ve learned how to use the Variables pane to help you find and fix problems, but there’s still more to learn!
Here are some techniques you might be interested in:
- How to interpret the different colors of inspected variables.
- Viewing variables in-line and within tooltips.
- Comparing variable values with your clipboard.
Check out the JetBrains documentation for examining a suspended program to learn about these techniques in-depth and much more.