Two developers can write the same app. One of them finds a bug in five minutes; the other spends a whole afternoon adding println statements. The difference is rarely talent. It is knowing the tools that Android Studio gives you for free: Logcat for messages, the Layout Inspector for the live UI tree, the Database Inspector for your Room tables, the Background Task Inspector for WorkManager, the Profiler for CPU, memory and network, and the Compose tooling for previews and live editing.
This lesson covers the Database Inspector and Background Task Inspector items from Unit 6 and Unit 7 of the Android Basics with Compose programme, the Accelerate with tooling item, and the Compose Preview and Layout Inspector tooling items of Pathway 1 of Jetpack Compose for Android developers (https://developer.android.com/courses/pathways/jetpack-compose-for-android-developers-1).
By the end you will be able to:
- Read and filter Logcat effectively.
- Debug with breakpoints, the Debugger window and conditional breakpoints.
- Inspect a running app with App Inspection: Database Inspector, Background Task Inspector, Network Inspector and Layout Inspector.
- Use Compose Preview features: multipreview, interactive and animation preview, device configurations.
- Use Live Edit and Apply Changes to iterate fast.
- Find memory, CPU and battery problems with the Profiler.
- Use lint, code inspections and keyboard shortcuts to move faster.
Big idea: observe first, then fix. A bug you can see in a tool takes minutes. A bug you guess at takes hours.
A note on menus. Android Studio is updated several times a year and window names, menu paths and tool layout move around. This lesson names the tools and their purpose and gives the usual menu path. If a path differs on your version, use Help → Find Action (or press Ctrl+Shift+A, Cmd+Shift+A on macOS) and type the tool's name.
- Android Studio setup, emulator, running an app (lessons 1–5).
- Room and the database (lesson 34).
- WorkManager (lesson 36).
- Recomposition (lesson 37) and Compose layouts (lessons 38–39).
- Coroutines and logging with
Log(lessons 23 and 28).
| Symptom | First tool | Why |
|---|---|---|
| "The app crashed" | Logcat | The stack trace is printed there |
| "This value is wrong at this point" | Debugger (breakpoint) | Pause and inspect variables |
| "The screen looks wrong" | Layout Inspector or Compose Preview | See the real UI tree and modifiers |
| "The list does not recompose or recomposes too much" | Layout Inspector (recomposition counts) | Counts per composable |
| "Data is not saved" | Database Inspector | Look at the real tables |
| "My background work never ran" | Background Task Inspector | See each worker and its state |
| "The server call fails" | Network Inspector | See each request and response |
| "The app is slow or janky" | Profiler | CPU, frames, memory |
| "The app uses too much memory" | Profiler → memory | Heap dumps and allocations |
| "I want to see my UI in 5 configurations" | Compose Preview | No emulator needed |
Most of these tools live in App Inspection (a tool window usually at the bottom of the IDE). To use it, run a debuggable build of your app on an emulator or device, and select the process from the dropdown.
Logcat shows system and app messages in real time. Open it with View → Tool Windows → Logcat or the Logcat icon on the bottom bar.
Writing good logs
import android.util.Log
private const val TAG = "OrderViewModel"
fun placeOrder(itemId: Int) {
Log.d(TAG, "placeOrder: itemId=$itemId")
try {
// ...
} catch (e: Exception) {
Log.e(TAG, "placeOrder failed for itemId=$itemId", e) // pass the exception as the 3rd argument
}
}
| Function | Level | Use for |
|---|---|---|
Log.v |
Verbose | Very detailed tracing |
Log.d |
Debug | Developer information |
Log.i |
Info | Normal milestones |
Log.w |
Warn | Something unexpected but handled |
Log.e |
Error | Failures; pass the Throwable to get the stack trace |
Never log secrets (passwords, tokens, personal data). Logs can be read by other tools on a rooted or debugging device, and by bug reports. In release builds, debug logs should be removed or disabled (R8 can strip them if configured, or use a logging library that is a no-op in release, lesson 49).
Filtering
The Logcat toolbar lets you:
- Pick the device and process (by default, your app only).
- Set the minimum level (for example Warn to reduce noise).
- Type a search query with filters such as
tag:OrderViewModel,level:error,package:mine,message:failed. The query box supports-for "not" (for example-tag:Choreographer) andage:5mfor "last five minutes". Check the Logcat help (the question mark in the filter field) for the syntax of your Studio version. - Switch to compact/standard view, and fold repeated lines.
- Save filters you reuse.
Reading a crash
A crash prints a stack trace. Read it from the top:
FATAL EXCEPTION: main
Process: com.example.classregister, PID: 12345
java.lang.IllegalStateException: Required value was null.
at com.example.classregister.DetailFragment.onViewCreated(DetailFragment.kt:42)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3123)
...
- The first line says what (
IllegalStateException, with the message). - The first line that mentions your package says where (
DetailFragment.kt:42). Studio makes it a clickable link. - Lines from
androidxandandroidframeworks below it tell you how it got there, which can matter, but start with your code.
If the trace has Caused by: lines, scroll to the last Caused by:; it is the root cause.
Instead of printing values, pause the program and look.
- Set a breakpoint: click in the gutter (left of the line numbers) next to a line. A red dot appears.
- Run in debug mode: click the bug icon (Debug) instead of Run. (Alternatively, Attach Debugger to Android Process if the app is already running.)
- When the line is reached, the app freezes and the Debug window shows the call stack (Frames) and Variables.
Controls (names are consistent between Studio versions; shortcuts are shown in the toolbar tooltips):
| Control | What it does |
|---|---|
| Resume | Continue to the next breakpoint |
| Step Over | Execute this line, go to the next line in this function |
| Step Into | Go inside the function being called |
| Step Out | Run until the current function returns |
| Run to Cursor | Run until the line where your cursor is |
| Evaluate Expression | Type any Kotlin expression and see its value |
Advanced breakpoints
- Conditional breakpoint: right-click the red dot and add a condition (for example
student.id == 7), so you stop only for the item you care about instead of 200 times in a loop. - Log message breakpoint: choose "Evaluate and log" instead of suspending, so you get a
printlnwithout editing code. - Exception breakpoint: in the Breakpoints dialog, break whenever a certain exception type is thrown.
- Watches: add an expression to the Watches pane to follow it across steps.
Debugging Compose and coroutines
Breakpoints work inside composables, but a composable can run many times (recomposition), so use a conditional breakpoint. In coroutines, the Debug window shows a Coroutines tab (when the coroutine debugger is available) listing active coroutines and their stacks. Stepping across a suspend call may land on a different thread; this is normal.
Open View → Tool Windows → App Inspection. Run your app (debug build) on a device or emulator. Choose the device and process. Tabs along the top include Network Inspector, Database Inspector and Background Task Inspector. Some Studio versions list the Layout Inspector in the same area; others use a separate window.
Database Inspector
You wrote a Room database in lesson 34. Instead of printing every row to Logcat, look at the tables.
- Run the Inventory or Class Register app (anything with Room) in debug mode.
- App Inspection → Database Inspector.
- In the left pane, expand the database file; double-click a table to see all rows in a grid.
- Live updates: tick Live updates and see rows change as you use the app.
- Run a query: open the Query tab, write a
SELECT * FROM student WHERE present = 1, run it, and use the results. You can edit cell values directly (for tables, not views) to test how your UI reacts to different data without changing code. - Export: right-click a table to export as CSV, or export the database file, which you can open in a desktop SQLite browser.
What it is good for:
- Confirm that an
@Insertreally saved the row. - Check foreign keys and defaults (
NULLwhere you expected a value). - Test your migrations: install version 1, insert rows, run version 2 and compare.
- Run the exact SQL your DAO uses (copy it out of the
@Querystring).
If the inspector says it cannot see the database, check that you are running a debuggable build, that the process is selected, and that the app has actually opened the database (it is created lazily on first access). In-memory databases (Room.inMemoryDatabaseBuilder) are not inspectable on disk, although the inspector can work with them in some versions.
Background Task Inspector
Lesson 36 taught WorkManager. The Background Task Inspector shows workers, jobs, alarms and wake locks your app has scheduled.
- Run an app that enqueues work (for example the Blur-O-Matic style app from lesson 36).
- App Inspection → Background Task Inspector.
- In the table view you see each Worker with its status: Enqueued, Running, Succeeded, Failed, Cancelled, Blocked. Click one to see its Description (class, tags, constraints, input and output data), Results, and WorkContinuation (the chain of dependent work).
- The graph view shows chains: A runs, then B, then C. If A fails, B and C never run and appear as Blocked or Cancelled.
- The timeline shows each task over time, together with alarms and wake locks.
- A Cancel button lets you stop a worker manually.
This answers questions that are otherwise very hard: Why did my sync never run? (look for Blocked by a constraint such as unmetered network), Why did my chain stop? (a failed parent), How many retries happened? (the attempt count in the description).
Network Inspector
When your Retrofit code (lesson 29) misbehaves, open the Network Inspector. It lists each request with URL, method, status, size and time, and the Connection View shows request and response headers and bodies (JSON is pretty-printed). Use it to catch a wrong URL, a 404, an unexpected response shape, or a request that is sent far more often than you intended. It works with common HTTP stacks such as HttpURLConnection and OkHttp (so Retrofit); other stacks may need extra setup.
The Layout Inspector shows the actual UI tree of your running app, which is invaluable when a screen looks wrong and you cannot tell why.
- Run the app. Open View → Tool Windows → Layout Inspector (the exact entry may be inside App Inspection on newer versions).
- Select your process.
- You see three panes: the Component Tree (hierarchy), a rendering of the screen (you can rotate it in 3D), and the Attributes pane for the selected element.
For View screens, the tree lists ConstraintLayout, TextView, etc. with their XML attributes. For Compose screens, it lists composables (Column, Row, Text, Button) and shows their modifiers, parameters, size and semantic information. For apps that mix both (lesson 43) it shows the Compose tree inside the ComposeView.
Useful tricks:
- Click a node in the screen to see which composable drew it, and use Go to source (right-click, or the Attributes toolbar) to jump to the code.
- 3D mode reveals overlapping or clipped layers.
- Recomposition counts (a checkbox in the Compose tree settings, called Show Recomposition Counts). Interact with the app, and the tree shows how many times each composable recomposed and skipped. A
Textthat recomposed 300 times while you scrolled is a performance clue (lesson 57). - Live updates keep the inspector in step as the app runs, at some performance cost. Turn it off if the app becomes slow.
- Inspect semantics (the accessibility tree) to check labels and roles (lessons 46–48).
Layout Inspector needs a debuggable build and, on older devices, extra permission, and it can slow down a weak emulator. Close it when not in use.
You used @Preview in lessons 15–22. The preview window can do far more.
Multipreview annotations
To see a composable in light and dark, large font, and different devices without stacking many annotations by hand, define a multipreview annotation:
package com.example.classregister.ui
import android.content.res.Configuration
import androidx.compose.ui.tooling.preview.Preview
@Preview(name = "Light", showBackground = true)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true)
@Preview(name = "Large font", fontScale = 1.5f, showBackground = true)
@Preview(name = "Small phone", device = "spec:width=320dp,height=640dp,dpi=320", showBackground = true)
annotation class ThemePreviews
Now one line puts all four previews on any composable:
import androidx.compose.runtime.Composable
@ThemePreviews
@Composable
private fun StudentRowPreview() {
MaterialTheme { StudentRow(Student(1, "Ada Okafor", present = true), onPresentChange = {}) }
}
Android's UI Tooling library also ships ready-made multipreview annotations, for example @PreviewScreenSizes, @PreviewFontScale, @PreviewLightDark and @PreviewDynamicColors, which are exactly this idea. Check the androidx.compose.ui.tooling.preview package for the current set.
PreviewParameter
Preview the same composable with many sets of data:
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
class StudentProvider : PreviewParameterProvider<Student> {
override val values = sequenceOf(
Student(1, "Ada", present = true),
Student(2, "A very long student name that will have to wrap onto a second line", present = false),
Student(3, "", present = false)
)
}
@Preview(showBackground = true)
@Composable
private fun StudentRowVariants(@PreviewParameter(StudentProvider::class) student: Student) {
MaterialTheme { StudentRow(student, onPresentChange = {}) }
}
This shows three previews: a normal row, a very long name and an empty name. Previewing edge cases catches layout bugs before you run the app.
Interactive, Animation and Deploy to Device modes
Above each preview you can choose:
- Interactive mode: click buttons and type into fields, as if on a device (with some limits).
- Animation Preview: scrub through
updateTransition,AnimatedVisibilityand similar animations (lesson 41). - Deploy Preview to Device (the phone icon) runs only that composable on your emulator or device without launching the full app.
Preview limits and tips
- A preview cannot access things that need a real device: network, databases,
LocalContextfeatures like real resources of other apps. Give composables plain parameters (state and lambdas), not a ViewModel, so they stay previewable. This is the same advice as state hoisting (lesson 24). - Use
showSystemUi = trueto show the status and navigation bars. - If a preview breaks, press Build & Refresh at the top of the preview. A red banner explains which line failed.
Waiting for a full build every time you change a colour is slow. Android Studio has two faster ways:
| Feature | What it does | Best for |
|---|---|---|
| Apply Changes (Apply Code Changes / Apply Changes and Restart Activity) | Pushes modified code or resources to the running app without a full reinstall | Non-UI logic, resources |
| Live Edit | Updates composable functions in the running app as you type or on save | Compose UI tuning: padding, colours, text |
To use Live Edit: open Settings → Editor → Live Edit and choose the mode (manual or automatic; the exact labels change between versions). Run your app on a device or emulator on a supported API level (see the documentation for the minimum), edit a composable, and watch the app update in a second or two. Limitations: structural changes (new classes, changes to function signatures, new resources) need a normal run; a status indicator at the top right of the editor tells you whether the last edit was applied or needs a rebuild.
The Android Profiler (View → Tool Windows → Profiler, or the Profile button) is where you investigate performance. Profile the release-like build whenever possible (lesson 49) because debug builds are slower and unrepresentative. The Studio can run the profiler on a profileable build for accurate results.
The main task types (names and layout change between Studio versions):
| Task | Question it answers |
|---|---|
| CPU / Callstack sampling, System Trace | Which functions use the time? Why is there jank? |
| Java/Kotlin memory (heap dump, allocations) | What objects are in memory and who holds them? |
| Energy / power | What wakes the device and drains battery? |
| Network | How much data is sent and received? |
A short, useful workflow for memory leaks:
- Rotate the screen and navigate back and forth several times.
- Click Capture heap dump.
- Search for your Activity or Fragment class: if there are several instances of the same screen that should be gone, you have a leak.
- Select an instance and see the References (what keeps it alive). Common leaks: a static or singleton holding a
Contextor a View, a listener never removed, a fragment binding never nulled (lesson 42).
For jank (stuttering), record a System Trace while scrolling and look at frames longer than 16 ms (at 60 Hz) in the Frame timeline. Long frames show which work ran on the main thread. Lesson 57 goes much deeper (Macrobenchmark and baseline profiles).
The programme's Accelerate with tooling theme is simple: spend 30 minutes learning shortcuts and you will save weeks of typing.
Shortcuts you should memorise (Windows/Linux, macOS in brackets)
| Action | Shortcut |
|---|---|
| Search everywhere | Double Shift |
| Find Action (any command) | Ctrl+Shift+A (Cmd+Shift+A) |
| Go to file | Ctrl+Shift+N (Cmd+Shift+O) |
| Go to class / symbol | Ctrl+N (Cmd+O) |
| Recent files | Ctrl+E (Cmd+E) |
| Show intention actions and quick fixes | Alt+Enter (Option+Return) |
| Generate code (constructors, overrides) | Alt+Insert (Cmd+N) |
| Reformat code | Ctrl+Alt+L (Cmd+Option+L) |
| Go to declaration | Ctrl+B or Ctrl+click (Cmd+B) |
| Find usages | Alt+F7 (Option+F7) |
| Rename (everywhere, safely) | Shift+F6 |
| Comment line | Ctrl+/ (Cmd+/) |
| Run / Debug | Shift+F10 / Shift+F9 (Ctrl+R / Ctrl+D, check your keymap) |
Keymaps vary; if a shortcut differs, Settings → Keymap lets you search by action.
Code completion and live templates
- Type
Log+ Tab expands live templates (Studio haslogd,loge,logm). Typecomp+ Tab in some project setups to scaffold a composable; check Settings → Editor → Live Templates, and create your own: for examplevmexpanding into a ViewModel class skeleton. - Postfix completion: type
list.size.ifand press Tab to wrap it in anif. Typevalue.varto create a variable. - Extract: select code, press Ctrl+Alt+M (Cmd+Option+M) to extract a function, Ctrl+Alt+V to extract a variable.
Lint and inspections
Lint is Studio's static analyser: it flags hard-coded text, missing content descriptions, unused resources, missing permissions, likely crashes and deprecated API usage. Run it from Analyze → Inspect Code, or from the command line with ./gradlew lint. For Compose there are extra lint checks (for example for modifier parameters and state reads). Treat red/yellow squiggles seriously; Alt+Enter offers a fix for many of them. The accessibility checks in lint come back in lesson 48.
Version control and local history
The Git tools (Commit window, Log tab, the Show History view) and Local History (right-click a file → Local History → Show History) can recover a deleted block even if you never committed it.
Gemini in Android Studio
Recent Android Studio versions include Gemini, an AI assistant that can explain code, suggest fixes and help generate code in the IDE. Availability, plans and features have changed over time, so check the official Android Studio documentation for what your version offers. Treat suggestions like code from a classmate: read it, understand it, test it.
Exercise 1: Find the crash in 60 seconds
The following code crashes when the user taps Save with an empty name. Run it, read Logcat, and name the exception, the file and line number. Then fix it with validation and a user-visible message.
fun save(name: String?) {
val trimmed = name!!.trim()
require(trimmed.isNotEmpty())
repository.insert(Student(0, trimmed))
}
In Logcat filter by level:error. If name is null the trace shows NullPointerException (from !!) pointing at the line name!!.trim() in your file; if the name is blank, IllegalArgumentException: Failed requirement. pointing at require. The first line in your own package gives the file and line.
Fix by not crashing, and tell the user:
fun save(name: String?): String? {
val trimmed = name?.trim().orEmpty()
if (trimmed.isEmpty()) return "Please enter a name" // message for the UI
repository.insert(Student(0, trimmed))
return null
}
Return an error value (or update a UiState with an error message) rather than throwing for ordinary user mistakes.
Exercise 2: Debug with a conditional breakpoint
A function loops over 500 students and one of them has a negative score. Set a breakpoint that stops only for that student, without adding any code.
Click the gutter on the line inside the loop that uses score, right-click the red dot, and type the condition student.score < 0 in the Condition field. Run in Debug mode. The program stops only when the condition is true; open Variables to see the offending student. Alternatively add an Exception breakpoint if the negative score causes a thrown exception.
Exercise 3: Prove the data was saved
Using the Database Inspector, prove that tapping Add in your Room app inserts exactly one row. Describe the steps, and what you would check if the table is empty.
Run in debug on an emulator, open App Inspection → Database Inspector, pick the app process and database, open the table, tick Live updates, and tap Add once: one new row should appear. If it is empty, check: (1) you selected the right process and the app has opened the database; (2) the DAO call actually runs (set a breakpoint or log inside the repository); (3) the insert is not running inside a cancelled coroutine (look for CancellationException in Logcat); (4) the app is using an in-memory database; (5) you are looking at the right database file (debug and release can differ).
Mistake #1: Debugging with only println. Fix: use breakpoints and the Variables pane; use log-message breakpoints for quick prints.
Mistake #2: Reading only the last line of a stack trace. Fix: read the top, then find your package's line, and the last Caused by:.
Mistake #3: Logging secrets. Fix: never log tokens, passwords or personal data.
Mistake #4: Inspecting a release build. Symptom: inspectors show nothing. Fix: use a debuggable build (or a profileable one for profiling).
Mistake #5: Profiling a debug build and trusting the numbers. Fix: profile a release-like build.
Mistake #6: Leaving Live updates and Layout Inspector on. Symptom: a sluggish emulator. Fix: turn them off when finished.
Mistake #7: Previews that need a ViewModel. Symptom: preview fails to render. Fix: hoist state and pass plain parameters.
Mistake #8: Ignoring lint warnings. Fix: run Inspect Code before each release and fix real issues.
Mistake #9: Not saving filters. Fix: save a Logcat filter for your package and tag.
Mistake #10: Changing the database schema without checking the inspector. Fix: inspect after migrating.
Question 1. Where do you look first when the app crashes, and what do you read?
Logcat (filter for errors). Read the exception type and message on the first line, then the first line from your own package for file and line, and the last Caused by: for the root cause.
Question 2. What can the Database Inspector do that Logcat cannot?
It shows live table contents of the running app, lets you run SQL queries, edit values, and export data, so you can verify what is actually stored.
Question 3. What does the Background Task Inspector show?
WorkManager workers, jobs, alarms and wake locks with their state (enqueued, running, succeeded, failed, cancelled, blocked), details, results and the chain of dependent work in a graph and a timeline.
Question 4. How do you find which composables recompose too often?
Use the Layout Inspector with recomposition counts enabled, interact with the app, and look for composables with high recomposition counts.
Question 5. Why use a multipreview annotation?
It bundles many @Preview configurations (light, dark, large font, small screen) into one annotation so that a single line previews a composable in all of them, catching layout and theme bugs early.
Go deeper: official resources
- Jetpack Compose for Android developers, Pathway 1: Getting started
- Android Studio user guide
- Android Studio download page
- Android Basics with Compose course
Project goal: You are given an app with five planted bugs. Use the tools in this lesson to find each one. Write a short report listing the tool you used and the cause.
Create the app (BugHunt, Empty Activity with Compose and Room if you wish; the single file below is enough for bugs 1, 2, 3 and 5):
Requirements:
- Crash: find the exception with Logcat and fix it.
- Wrong value: find with a conditional breakpoint and fix it.
- Over-recomposition: find it with Layout Inspector recomposition counts and fix it.
- (If you have Room) an insert that never saves: find it with the Database Inspector.
- A preview that crashes: fix it by hoisting state and add a multipreview annotation.
Stretch goal: record a heap dump after rotating the screen ten times and check there is only one MainActivity instance.
A single-file broken app and its fixes:
package com.example.bughunt
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { MaterialTheme { BugHunt() } }
}
}
private const val TAG = "BugHunt"
data class Item(val id: Int, val name: String, val price: Int)
val items = List(200) { Item(it, "Item $it", it * 10) }
// BUG 1 (crash): original used items[200] -> IndexOutOfBoundsException. FIX: guard the index.
fun safeItem(index: Int): Item? = items.getOrNull(index)
// BUG 2 (wrong value): original summed prices with `price - 1`. FIX: sum the real price.
fun total(list: List<Item>): Int = list.sumOf { it.price }
@Composable
fun BugHunt() {
var taps by remember { mutableIntStateOf(0) }
// BUG 3 (over-recomposition): original computed `expensive()` directly in the body so the
// whole screen redid it on every tap. FIX: remember it, keyed on what it really depends on.
val summary by remember { derivedStateOf { "Total: ${total(items)}" } }
Column(Modifier.padding(16.dp)) {
Text(summary)
Button(onClick = {
taps++
Log.d(TAG, "taps=$taps item=${safeItem(taps + 199)}") // BUG 1 fixed: null instead of crash
}) { Text("Tap ($taps)") }
LazyColumn {
items(items, key = { it.id }) { Text(it.name) }
}
}
}
// BUG 5 (preview crash): original preview called a ViewModel. FIX: preview with plain state.
@Preview(showBackground = true)
@Composable
private fun BugHuntPreview() {
MaterialTheme { BugHunt() }
}
Report template:
| Bug | Tool | Cause | Fix |
|---|---|---|---|
| 1 Crash | Logcat | IndexOutOfBoundsException at the file/line shown |
getOrNull |
| 2 Wrong total | Debugger (conditional breakpoint, Watches) | price - 1 |
sumOf { it.price } |
| 3 Recomposition | Layout Inspector counts | Expensive work in the body | derivedStateOf / remember |
| 4 Not saved | Database Inspector | DAO insert called in a cancelled scope or on the wrong database | Call insert from viewModelScope and verify live rows |
| 5 Preview | Preview error banner | ViewModel needs an Android context | Preview with plain parameters |
Open your finished project in Android Studio. Check that the .gitignore file the wizard created is in the project root. It keeps local.properties, build folders and keystores out of Git. Never commit passwords or signing keys.
Choose Git > Create Git Repository… (or VCS > Enable Version Control Integration in older versions), select the project folder, then open Git > Commit, tick every file, and write the message “Add Android lesson 44 project”.
On github.com click New repository, name it android-lesson-44-android-studio-power-tools, set it to Public, leave Add a README unticked, and click Create repository.
Back in Android Studio choose Git > Manage Remotes…, add the repository URL as origin, then choose Git > Push… and push your main branch. Prefer the terminal? Run git remote add origin <your-url> then git push -u origin main.
Add a README.md with a screenshot of your app running and a short description. Employers and clients read READMEs first. Your project is now part of your public portfolio. 🎉
Lesson 44 complete! 🎉
You covered:
- Lesson Introduction
- Prerequisite Concepts
- Conceptual Understanding: Which Tool for Which Problem?
- Step-by-Step Build 1: Logcat
- Step-by-Step Build 2: The Debugger
- Step-by-Step Build 3: App Inspection
- Step-by-Step Build 4: Layout Inspector
- Step-by-Step Build 5: Compose Preview
- Step-by-Step Build 6: Live Edit and Apply Changes
- Step-by-Step Build 7: The Profiler
- Step-by-Step Build 8: Accelerating Yourself: Inspections, Lint and Shortcuts
- Guided Practice Exercises
- Common Mistakes (and How to Fix Them)
- Knowledge Check