Here, check for the link for reference.
In here you create a class say ExceptionHandler that implements java.lang.Thread.UncaughtExceptionHandler..
Inside this class you will do your life saving stuff like creating stacktrace and gettin ready to upload error report etc….
Now comes the important part i.e. How to catch that exception.
Though it is very simple. Copy following line of code in your each Activity just after the call of super method in your overridenonCreatemethod.Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this));Your Activity may look something like this…
public class ForceClose extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this)); setContentView(R.layout.main); } }
And this is a sample ExceptionHandler class:
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
private static final String TAG = "ExceptionHandler";
@Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable exception) {
Log.e(TAG, "uncaughtException: " + "n" + getErrorReport(exception));
Log.i(TAG, getDeviceInfo());
Log.i(TAG, getFirmwareInfo());
stopTheApp();
}
private String getErrorReport(@NonNull Throwable exception) {
ApplicationErrorReport.CrashInfo crashInfo = new ApplicationErrorReport.CrashInfo(exception);
return "nCAUSE OF ERRORn" +
crashInfo.stackTrace;
}
private String getDeviceInfo() {
return
"nDEVICE INFORMATIONn" +
"Brand: " +
Build.BRAND +
"n" +
"Device: " +
Build.DEVICE +
"n" +
"Model: " +
Build.MODEL +
"n" +
"Id: " +
Build.ID +
"n" +
"Product: " +
Build.PRODUCT +
"n";
}
private String getFirmwareInfo() {
return "nFIRMWAREn" +
"SDK: " +
Build.VERSION.SDK_INT +
"n" +
"Release: " +
Build.VERSION.RELEASE +
"n" +
"Incremental: " +
Build.VERSION.INCREMENTAL +
"n";
}
private void stopTheApp() {
android.os.Process.killProcess(android.os.Process.myPid());
}
}
Исключения
try
Оператор throw
Оператор throws
Оператор finally
Встроенные исключения Java
Создание собственных классов исключений
Исключение — это нештатная ситуация, ошибка во время выполнения программы. Самый простой пример — деление на ноль. Можно вручную отслеживать возникновение подобных ошибок, а можно воспользоваться специальным механизмом исключений, который упрощает создание больших надёжных программ, уменьшает объём необходимого кода и повышает уверенность в том, что в приложении не будет необработанной ошибки.
В методе, в котором происходит ошибка, создаётся и передаётся специальный объект. Метод может либо обработать исключение самостоятельно, либо пропустить его. В любом случае исключение ловится и обрабатывается. Исключение может появиться благодаря самой системе, либо вы сами можете создать его вручную. Системные исключения возникают при неправильном использовании языка Java или запрещённых приёмов доступа к системе. Ваши собственные исключения обрабатывают специфические ошибки вашей программы.
Вернёмся к примеру с делением. Деление на нуль может предотвратить проверкой соответствующего условия. Но что делать, если знаменатель оказался нулём? Возможно, в контексте вашей задачи известно, как следует поступить в такой ситуации. Но, если нулевой знаменатель возник неожиданно, деление в принципе невозможно, и тогда необходимо возбудить исключение, а не продолжать исполнение программы.
Существует пять ключевых слов, используемых в исключениях: try, catch, throw, throws, finally. Порядок обработки исключений следующий.
Операторы программы, которые вы хотите отслеживать, помещаются в блок try. Если исключение произошло, то оно создаётся и передаётся дальше. Ваш код может перехватить исключение при помощи блока catch и обработать его. Системные исключения автоматически передаются самой системой. Чтобы передать исключение вручную, используется throw. Любое исключение, созданное и передаваемое внутри метода, должно быть указано в его интерфейсе ключевым словом throws. Любой код, который следует выполнить обязательно после завершения блока try, помещается в блок finally
Схематически код выглядит так:
try {
// блок кода, где отслеживаются ошибки
}
catch (тип_исключения_1 exceptionObject) {
// обрабатываем ошибку
}
catch (тип_исключения_2 exceptionObject) {
// обрабатываем ошибку
}
finally {
// код, который нужно выполнить после завершения блока try
}
Существует специальный класс для исключений Trowable. В него входят два класса Exception и Error.
Класс Exception используется для обработки исключений вашей программой. Вы можете наследоваться от него для создания собственных типов исключений. Для распространённых ошибок уже существует класс RuntimeException, который может обрабатывать деление на ноль или определять ошибочную индексацию массива.
Класс Error служит для обработки ошибок в самом языке Java и на практике вам не придётся иметь с ним дело.
Прежде чем научиться обрабатывать исключения, нам (как и нормальному любопытному коту) хочется посмотреть, а что происходит, если ошибку не обработать. Давайте разделим число котов в вашей квартире на ноль, хотя мы и знаем, что котов на ноль делить нельзя!
int catNumber;
int zero;
catNumber = 1; // у меня один кот
zero = 0; // ноль, он и в Африке ноль
int result = catNumber / zero;
Я поместил код в обработчик щелчка кнопки. Когда система времени выполнения Java обнаруживает попытку деления на ноль, она создаёт объект исключения и передаёт его. Да вот незадача, никто не перехватывает его, хотя это должны были сделать вы. Видя вашу бездеятельность, объект перехватывает стандартный системный обработчик Java, который отличается вредных характером. Он останавливает вашу программу и выводит сообщение об ошибке, которое можно увидеть в журнале LogCat:
Caused by: java.lang.ArithmeticException: divide by zero at ru.alexanderklimov.test.MainActivity.onClick(MainActivity.java:79)
Как видно, созданный объект исключения принадлежит к классу ArithmeticException, далее системный обработчик любезно вывел краткое описание ошибки и место возникновения.
Вряд ли пользователи вашей программы будут довольны, если вы так и оставите обработку ошибки системе. Если программа будет завершаться с такой ошибкой, то скорее всего вашу программу просто удалят. Посмотрим, как мы можем исправить ситуацию.
Поместим проблемный код в блок try, а в блоке catch обработаем исключение.
int catNumber;
int zero;
try { // мониторим код
catNumber = 1; // у меня один кот
zero = 0; // ноль, он и в Африке ноль
int result = catNumber / zero;
Toast.makeText(this, "Не увидите это сообщение!", Toast.LENGTH_LONG).show();
} catch (ArithmeticException e) {
Toast.makeText(this, "Нельзя котов делить на ноль!", Toast.LENGTH_LONG).show();
}
Toast.makeText(this, "Жизнь продолжается", Toast.LENGTH_LONG).show();
Теперь программа аварийно не закрывается, так как мы обрабатываем ситуацию с делением на ноль.
В данном случае мы уже знали, к какому классу принадлежит получаемая ошибка, поэтому в блоке catch сразу указали конкретный тип. Обратите внимание, что последний оператор в блоке try не срабатывает, так как ошибка происходит раньше строчкой выше. Далее выполнение передаётся в блок catch, далее выполняются следующие операторы в обычном порядке.
Операторы try и catch работают совместно в паре. Хотя возможны ситуации, когда catch может обрабатывать несколько вложенных операторов try.
Если вы хотите увидеть описание ошибки, то параметр e и поможет увидеть ёго.
catch (ArithmeticException e) {
Toast.makeText(this, e + ": Нельзя котов делить на ноль!", Toast.LENGTH_LONG).show();
}
По умолчанию, класс Trowable, к которому относится ArithmeticException возвращает строку, содержащую описание исключения. Но вы можете и явно указать метод e.toString.
Несколько исключений
Фрагмент кода может содержать несколько проблемных мест. Например, кроме деления на ноль, возможна ошибка индексации массива. В таком случае вам нужно создать два или более операторов catch для каждого типа исключения. Причём они проверяются по порядку. Если исключение будет обнаружено у первого блока обработки, то он будет выполнен, а остальные проверки пропускаются и выполнение программы продолжается с места, который следует за блоком try/catch.
int catNumber;
int zero;
try { // мониторим код
catNumber = 1; // у меня один кот
zero = 1; // ноль, он и в Африке ноль
int result = catNumber / zero;
// Создадим массив из трёх котов
String[] catNames = {"Васька", "Барсик", "Мурзик"};
catNames[3] = "Рыжик";
Toast.makeText(this, "Не увидите это сообщение!", Toast.LENGTH_LONG).show();
} catch (ArithmeticException e) {
Toast.makeText(this, e.toString() + ": Нельзя котов делить на ноль!", Toast.LENGTH_LONG).show();
}
catch (ArrayIndexOutOfBoundsException e) {
Toast.makeText(this, "Ошибка: " + e.toString(), Toast.LENGTH_LONG).show();
}
Toast.makeText(this, "Жизнь продолжается", Toast.LENGTH_LONG).show();
В примере мы добавили массив с тремя элементами, но обращаемся к четвёртому элементу, так как забыли, что отсчёт у массива начинается с нуля. Если оставить значение переменной zero равным нулю, то сработает обработка первого исключения деления на ноль, и мы даже не узнаем о существовании второй ошибки. Но допустим, что в результате каких-то вычислений значение переменной стало равно единице. Тогда наше исключение ArithmeticException не сработает. Но сработает новое добавленное исключение ArrayIndexOutOfBoundsException. А дальше всё пойдёт как раньше.
Тут всегда нужно помнить одну особенность. При использовании множественных операторов catch обработчики подклассов исключений должные находиться выше, чем обработчики их суперклассов. Иначе, суперкласс будет перехватывать все исключения, имея большую область перехвата. Иными словами, Exception не должен находиться выше ArithmeticException и ArrayIndexOutOfBoundsException. К счастью, среда разработки сама замечает непорядок и предупреждает вас, что такой порядок не годится. Увидев такую ошибку, попробуйте перенести блок обработки исключений ниже.
Вложенные операторы try
Операторы try могут быть вложенными. Если вложенный оператор try не имеет своего обработчика catch для определения исключения, то идёт поиск обработчика catch у внешнего блока try и т.д. Если подходящий catch не будет найден, то исключение обработает сама система (что никуда не годится).
Оператор throw
Часть исключений может обрабатывать сама система. Но можно создать собственные исключения при помощи оператора throw. Код выглядит так:
throw экземпляр_Throwable
Вам нужно создать экземпляр класса Throwable или его наследников. Получить объект класса Throwable можно в операторе catch или стандартным способом через оператор new.
Мы могли бы написать такой код для кнопки:
Cat cat;
public void onClick(View view) {
if(cat == null){
throw new NullPointerException("Котик не инициализирован");
}
}
Мы объявили объект класса Cat, но забыли его проинициализировать, например, в onCreate(). Теперь нажатие кнопки вызовет исключение, которое обработает система, а в логах мы можем прочитать сообщение об ошибке. Возможно, вы захотите использовать другое исключение, например, throw new UnsupportedOperationException(«Котик не инициализирован»);.
В любом случае мы передали обработку ошибки системе. В реальном приложении вам нужно обработать ошибку самостоятельно.
Поток выполнения останавливается непосредственно после оператора throw и другие операторы не выполняются. При этом ищется ближайший блок try/catch соответствующего исключению типа.
Перепишем пример с обработкой ошибки.
public void onClick(View view) {
if (cat == null) {
try {
throw new NullPointerException("Кота не существует");
} catch (NullPointerException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
Мы создали новый объект класса NullPointerException. Многие классы исключений кроме стандартного конструктора по умолчанию с пустыми скобками имеют второй конструктор с строковым параметром, в котором можно разместить подходящую информацию об исключении. Получить текст из него можно через метод getMessage(), что мы и сделали в блоке catch.
Теперь программа не закроется аварийно, а будет просто выводить сообщения в всплывающих Toast.
Оператор throws
Если метод может породить исключение, которое он сам не обрабатывает, он должен задать это поведение так, чтобы вызывающий его код мог позаботиться об этом исключении. Для этого к объявлению метода добавляется конструкция throws, которая перечисляет типы исключений (кроме исключений Error и RuntimeException и их подклассов).
Общая форма объявления метода с оператором throws:
тип имя_метода(список_параметров) throws список_исключений {
// код внутри метода
}
В фрагменте список_исключений можно указать список исключений через запятую.
Создадим метод, который может породить исключение, но не обрабатывает его. А в щелчке кнопки вызовем его.
// Метод без обработки исключения
public void createCat(){
Toast.makeText(this, "Вы создали котёнка", Toast.LENGTH_LONG).show();
throw new NullPointerException("Кота не существует");
}
// Щелчок кнопки
public void onClick(View v) {
createCat();
}
Если вы запустите пример, то получите ошибку. Исправим код.
// Без изменений
public void createCat() throws NullPointerException {
Toast.makeText(this, "Вы создали котёнка", Toast.LENGTH_LONG).show();
throw new NullPointerException("Кота не существует");
}
// Щелчок кнопки
public void onClick(View v) {
try {
createCat();
} catch (NullPointerException e) {
// TODO: handle exception
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
}
}
Мы поместили вызов метода в блок try и вызвали блок catch с нужным типом исключения. Теперь ошибки не будет.
Оператор finally
Когда исключение передано, выполнение метода направляется по нелинейному пути. Это может стать источником проблем. Например, при входе метод открывает файл и закрывает при выходе. Чтобы закрытие файла не было пропущено из-за обработки исключения, был предложен механизм finally.
Ключевое слово finally создаёт блок кода, который будет выполнен после завершения блока try/catch, но перед кодом, следующим за ним. Блок будет выполнен, независимо от того, передано исключение или нет. Оператор finally не обязателен, однако каждый оператор try требует наличия либо catch, либо finally.
Существуют несколько готовых системных исключений. Большинство из них являются подклассами типа RuntimeException и их не нужно включать в список throws. Вот небольшой список непроверяемых исключений.
- ArithmeticException — арифметическая ошибка, например, деление на нуль
- ArrayIndexOutOfBoundsException — выход индекса за границу массива
- ArrayStoreException — присваивание элементу массива объекта несовместимого типа
- ClassCastException — неверное приведение
- EnumConstantNotPresentException — попытка использования неопределённого значения перечисления
- IllegalArgumentException — неверный аргумент при вызове метода
- IllegalMonitorStateException — неверная операция мониторинга
- IllegalStateException — некорректное состояние приложения
- IllegalThreadStateException — запрашиваемая операция несовместима с текущим потоком
- IndexOutofBoundsException — тип индекса вышел за допустимые пределы
- NegativeArraySizeException — создан массив отрицательного размера
- NullPointerException — неверное использование пустой ссылки
- NumberFormatException — неверное преобразование строки в числовой формат
- SecurityException — попытка нарушения безопасности
- StringIndexOutOfBounds — попытка использования индекса за пределами строки
- TypeNotPresentException — тип не найден
- UnsupportedOperationException — обнаружена неподдерживаемая операция
Список проверяемых системных исключений, которые можно включать в список throws.
- ClassNotFoundException — класс не найден
- CloneNotSupportedException — попытка клонировать объект, который не реализует интерфейс Cloneable
- IllegalAccessException — запрещен доступ к классу
- InstantiationException — попытка создать объект абстрактного класса или интерфейса
- InterruptedException — поток прерван другим потоком
- NoSuchFieldException — запрашиваемое поле не существует
- NoSuchMethodException — запрашиваемый метод не существует
- ReflectiveOperationException — исключение, связанное с рефлексией
Создание собственных классов исключений
Система не может предусмотреть все исключения, иногда вам придётся создать собственный тип исключения для вашего приложения. Вам нужно наследоваться от Exception (напомню, что этот класс наследуется от Trowable) и переопределить нужные методы класса Throwable. Либо вы можете наследоваться от уже существующего типа, который наиболее близок по логике с вашим исключением.
- final void addSuppressed(Throwable exception) — добавляет исключение в список подавляемых исключений (JDK 7)
- Throwable fillInStackTrace() — возвращает объект класса Throwable, содержащий полную трассировку стека.
- Throwable getCause() — возвращает исключение, лежащее под текущим исключение или null
- String getLocalizedMessage() — возвращает локализованное описание исключения
- String getMessage() — возвращает описание исключения
- StackTraceElement[] getStackTrace() — возвращает массив, содержащий трассировку стека и состояний из элементов класса StackTraceElement
- final Throwable[] getSuppressed() — получает подавленные исключения (JDK 7)
- Throwable initCause(Throwable exception) — ассоциирует исключение с вызывающим исключением. Возвращает ссылку на исключение.
- void printStackTrace() — отображает трассировку стека
- void printStackTrace(PrintStream stream) — посылает трассировку стека в заданный поток
- void printStackTrace(PrintWriter stream) — посылает трассировку стека в заданный поток
- void setStackTrace(StackTraceElement elements[]) — устанавливает трассировку стека для элементов (для специализированных приложений)
- String toString() — возвращает объект класса String, содержащий описание исключения.
Самый простой способ — создать класс с конструктором по умолчанию.
// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.
package ru.alexanderklimov.exception;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void testMethod() throws HungryCatException{
System.out.println("Возбуждаем HungryCatException из метода testMethod()");
throw new HungryCatException(); // конструктор по умолчанию
}
public void onClick(View view) {
try {
testMethod();
} catch (HungryCatException e) {
e.printStackTrace();
System.out.println("Наше исключение перехвачено");
}
}
class HungryCatException extends Exception{
}
}
Мы создали собственный класс HungryCatException, в методе testMethod() его возбуждаем, а по нажатию кнопки вызываем этот метод. В результате наше исключение сработает.
Создать класс исключения с конструктором, который получает аргумент-строку, также просто.
// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.
package ru.alexanderklimov.exception;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void testMethod() throws HungryCatException {
System.out.println("Возбуждаем HungryCatException из метода testMethod()");
throw new HungryCatException(); // конструктор по умолчанию
}
public void testMethod2() throws HungryCatException {
System.out.println("Возбуждаем HungryCatException из метода testMethod2()");
throw new HungryCatException("Создано во втором методе");
}
public void onClick(View view) {
try {
testMethod();
} catch (HungryCatException e) {
e.printStackTrace();
System.out.println("Наше исключение перехвачено");
}
try {
testMethod2();
} catch (HungryCatException e) {
e.printStackTrace();
}
}
class HungryCatException extends Exception {
HungryCatException() {
}
HungryCatException(String msg) {
super(msg);
}
}
}
Ещё вариант. Добавим также метод toString().
class CustomException extends Exception {
String message;
CustomException(String str) {
message = str;
}
public String toString() {
return ("Custom Exception Occurred: " + message);
}
}
// где-то вызываем
try {
throw new CustomException("This is a custom message");
} catch (CustomException e) {
System.out.println(e);
}
Теперь класс содержит два конструктора. Во втором конструкторе используется конструктор родительского класса с аргументом String, вызываемый ключевым словом super.
Перехват произвольных исключений
Можно создать универсальный обработчик, перехватывающий любые типы исключения. Осуществляется это перехватом базового класса всех исключений Exception:
cacth(Exception e) {
Log.w("Log", "Перехвачено исключение");
}
Подобная конструкция не упустит ни одного исключения, поэтому её следует размещать в самом конце списка обработчиков, во избежание блокировки следующих за ней обработчиков исключений.
Основные правила обработки исключений
Используйте исключения для того, чтобы:
- обработать ошибку на текущем уровне (избегайте перехватывать исключения, если не знаете, как с ними поступить)
- исправить проблему и снова вызвать метод, возбудивший исключение
- предпринять все необходимые действия и продолжить выполнение без повторного вызова действия
- попытаться найти альтернативный результат вместо того, который должен был бы произвести вызванный метод
- сделать все возможное в текущем контексте и заново возбудить это же исключение, перенаправив его на более высокий уровень
- сделать все, что можно в текущем контексте, и возбудить новое исключение, перенаправив его на более высокий уровень
- завершить работу программы
- упростить программу (если используемая схема обработки исключений делает все только сложнее, значит, она никуда не годится)
- добавить вашей библиотеке и программе безопасности
Реклама
There could be many ways of handling API responses coming from servers in android but do you use a good way of handling it? In this article, we’ll see API responses handled with the help of Kotlin Sealed Class while using the Retrofit library for API calls. This sealed class approach fits perfectly in this case: If the user is expecting data in UI, then we need to send the errors all the way to our UI to notify the user & to make sure the user won’t just see a blank screen or experience unexpected UI.
Make Sealed Class
Note: We are following MVVM Architecture and using Retrofit with Kotlin Coroutines for api calls in background.
Just create a generic sealed class named Resource in a separate file in your data package or utils/others package. We have named it Resource & created it generic because we’ll use this class to wrap our different types of API responses. Basically, we need to update our UI on these three events i.e. Success, Error, Loading. So, we have created these as child classes of Resources to represent different states of UI. Now, in case of success, we’ll get data so we’ll wrap it with Resource. Success and in case of error, we’ll wrap the error message with Resource. Error & in case of loading we’ll just return Resource.Loading object (you can also alter it to wrap your loading data according to your needs).
Kotlin
sealed class Resource<T>(
val data: T? = null,
val message: String? = null
) {
class Success<T>(data: T) : Resource<T>(data = data)
class Error<T>(errorMessage: String) : Resource<T>(message = errorMessage)
class Loading<T> : Resource<T>()
}
Let’s also have a look at our ApiService Interface (see just below), here in every suspended API call function we are wrapping our API call response with Retrofit’s Response class because it provides us some extra info about API calls. For example: whether calls getting successful or not, error code, etc. We’ll call these suspended functions from our repositories… we’ll see that in while. Don’t get confused in our custom Resource class and Retrofit’s Response class. Resource class just represents different states of UI while Retrofit’s Response class gives us extra info about API calls.
Kotlin
interface ExampleApiService {
@GET("example/popular_articles")
suspend fun fetchPopularArticles(): Response<PopularArticlesResponse>
@GET("example/new_articles")
suspend fun fetchNewArticles(): Response<NewArticlesResponse>
}
Now let’s jump on to the main part of API success/error handling
You must be having different repositories in your project according to your needs and all your api calls must be happening through these repositories we need to do error handling of each & every API call. So, we need to wrap each API call inside the try-catch block. But wait… writing a try-catch block for every api call isn’t a good idea. So, let’s create a BaseRepository & write a common suspend function there named safeApiCall (any name which you like) which will be responsible to handle api responses of each & every api call.
Kotlin
abstract class BaseRepo() {
suspend fun <T> safeApiCall(apiToBeCalled: suspend () -> Response<T>): Resource<T> {
return withContext(Dispatchers.IO) {
try {
val response: Response<T> = apiToBeCalled()
if (response.isSuccessful) {
Resource.Success(data = response.body()!!)
} else {
val errorResponse: ExampleErrorResponse? = convertErrorBody(response.errorBody())
Resource.Error(errorMessage = errorResponse?.failureMessage ?: "Something went wrong")
}
} catch (e: HttpException) {
Resource.Error(errorMessage = e.message ?: "Something went wrong")
} catch (e: IOException) {
Resource.Error("Please check your network connection")
} catch (e: Exception) {
Resource.Error(errorMessage = "Something went wrong")
}
}
}
private fun convertErrorBody(errorBody: ResponseBody?): ExampleErrorResponse? {
return try {
errorBody?.source()?.let {
val moshiAdapter = Moshi.Builder().build().adapter(ExampleErrorResponse::class.java)
moshiAdapter.fromJson(it)
}
} catch (exception: Exception) {
null
}
}
}
safeApiCall is having suspended lambda function named ‘api’ that returns data wrapped in Resource so that safeApiCall could be able to receive our every api call function. As safeApiCall is a suspend function so we are using withContext coroutine scope with Dispatchers.IO, this way all network calls will run on the Input/Output background thread. Whenever a network call fails it throws an exception so we need to call our lambda function ‘apiToBeCalled’ inside the try-catch block and here if api call gets successful then we’ll wrap our success response data in Resource. Success object & we’ll return it through safeApiCall and if api call gets failed then we’ll catch that particular exception in one of the following catch blocks:
- When something will went wrong on the server when processing an HTTP request then it’ll throw an HTTP Exception.
- When the user won’t be having valid internet/wifi connection then it’ll throw a Java IO Exception.
- If any other exception will occur then it’ll simply come in general Exception block (put that block in last). You can also add other exceptions catch block according to your specific case before that last one general exception catch block.
Now in catch blocks, we can simply send our own customize error messages or error messages getting from exceptions in Resource.Error object.
Note: In case if network call doesn’t fail & doesn’t throw an exception instead if it is sending it’s own custom error json response (sometimes happens in case of Authentication failure/Token expires/Invalid api key/etc) then we won’t get an exception instead Retrofit’s Response class will help us determine by returning true/false on response.is Successful. In the above code also we are handling that inside try block, if it returns false then inside else case we’ll parse that api’s own custom error json response (see example below) into a pojo class created by us i.e. ExampleErrorResponse. For parsing this json response to our ExampleErrorResponse pojo, we are using Moshi library in our convertErrorBody function that’ll return ExampleErrorResponse object & then we can get API’s custom error response.
{
“status”: ”UnSuccessful”,
“failure_message” : ”Invalid api key or api key expires”
}
So, ultimately our safeApiCall function will return our response wrapped in Resource class. it could be either Success or Error. If you have understood up till this much then shoutout to you. You have won most of the battle.
Now let’s change the way of calling Apis in your current repositories
We’ll extend our all repositories from BaseRepo so that we can access safeApiCall function there. And inside our repo’s functions like getPopularArticles we’ll pass our ApiService’s function ( for ex: apiService.fetchPopularArticles() ) as a lambda argument in safeApiCall and then safeApiCall will simply handle its success/error part then ultimately it’ll return Resource<T> that’ll go all the way top to UI through ViewModel.
Note: Remember when we’ll call getPopularArticles function in viewmodel (see example below) then first we’ll post Resource.Loading object in mutable live data such that our fragment/activity can observe this loading state and can update the UI to show loading state and then as we’ll get our api response ready, it’ll be posted in mutable live data again so that UI can get this response (success/error) update again and can show data in UI or update itself accordingly.
Kotlin
class ExampleRepo(private val apiService: ExampleApiService) : BaseRepo() {
suspend fun getPopularArticles() : Resource<PopularArticlesResponse> {
return safeApiCall { apiService.fetchPopularArticles() }
}
suspend fun getPublishedArticles() : Resource<NewlyPublishedArticlesResponse> = safeApiCall { apiService.fetchPublishedArticles() }
}
ViewModel
Kotlin
class ExampleViewModel (private val exampleRepo: ExampleRepo) {
private val _popularArticles = MutableLiveData<Resource<PopularArticlesResponse>>()
val popularArticles: LiveData<Resource<PopularArticlesResponse>> = _popularArticles
init {
getPopularArticles()
}
private fun getPopularArticles() = viewModelScope.launch {
_popularArticles.postValue(Resource.Loading())
_popularArticles.postValue(exampleRepo.getPopularArticles())
}
}
That’s it, now we can observe all the states i.e. Success, Error, Loading in our UI easily. That was about api error handling or api response handling in a better way.
Last Updated :
10 Jan, 2023
Like Article
Save Article
Overview
When building Android apps, your app is bound to crash from time to time or exhibit strange unexpected behavior. You know you have experienced a runtime exception when you see this in your emulator or device:
Don’t worry though! This is totally normal and there’s a specific set of steps you can take to solve these. Refer to our guide below and/or these debugging slides for more a detailed look at debugging crashes and investigating unexpected problems with your app.
Debugging Mindset
As an Android developer, you’ll need to cultivate a «debugging mindset» as well as build up defensive programming practices that make writing error-prone code less likely. In addition, you’ll often find yourself in the role of a coding investigator in order to understand where and why an app is crashing or not working as expected. A few key principles about debugging are captured below:
- Just because a program runs, doesn’t mean it’s going to work as you expected. This class of issues are known as runtime errors. This is contrast to compile-time errors which prevent an app from running and are often easier to catch.
- Think of debugging as an opportunity to fill gaps in knowledge. Debugging is an opportunity to understand your app better than you did before and hopefully sharpen your ability to write correct code in the future by programming defensively.
- Debugging is a vital part of the software development process. Often you may find yourself on some days spending more time debugging crashes or unexpected behavior then writing new code. This is entirely normal as a mobile engineer.
Debugging Principles
The following high-level principles should be applied when faced with an unexpected app behavior during investigation:
- Replicate. Convince yourself that there is an issue in the code that can be repeatedly reproduced by following the same steps. Before assuming the code is broken, try restarting the emulator, trying the app on a device and/or fully re-building the app.
- Reduce. Try to isolate or reduce the code surrounding the issue and figure out the simplest way to reproduce what’s occurring. Comment out or remove extraneous code that could be complicating the issue.
- Research. If you are running into a major unexpected issue, you are probably not alone. Search Google for the behavior using any descriptive identifiers. Visit the issue tracker for the component you are seeing issues with. Search stackoverflow for posts about the same issue.
Armed with this mindset and the above principles, let’s take a look at how to debug and investigate issues that arise within our apps.
Debugging Procedure
When you see your app crash and close, the basic steps for diagnosing and resolving this are outlined below:
- Find the final exception stack trace within the Android Monitor (logcat)
- Identify the exception type, message, and file with line number
- Open the file within your app and find the line number
- Look at the exception type and message to diagnose the problem
- If the problem is not familiar, google around searching for answers
- Make fixes based on the proposed solutions and re-run the app
- Repeat until the crash no longer occurs
This process nearly always starts with an unexpected app crash as we’ll see below.
Witnessing the Crash
Suppose we were building a simple movie trailer app called Flixster that lets the users browse new movies and watch trailers from Youtube. Imagine we ran the app, and we wanted to play the trailer and we saw this crash instead:
First off though when you see the crash dialog, don’t press OK on that dialog until after you’ve already went through these steps below to identify the stacktrace details.
Setting Up Error Filter
First, within Android Studio, be sure to setup your Android Monitor to filter for «Errors» only to reduce noise:
- Select «Error» as the log level to display
- Select «Show only selected application» to filter messages
This will set you up to see only serious issues as they come up.
Note: See this blog post for improving the coloring of errors or logs in LogCat and for other related tools.
Find the Stack Trace
Now let’s go into Android Studio and select open up the «Android Monitor». Expand the monitor so you can read the log messages easily.
- Scroll to the bottom of the error looking for a line that says
Caused byall the way at the bottom of the stack trace block. The «original cause» towards the bottom of the block is the important part of the error, ignore most of the rest of the stack trace above that. - Locate that bottom-most
Caused byline as well as the line that has the blue link with the name of your activity i.eVideoActivity.java:13. Copy them onto your clipboard and paste them both into a separate text file for easy review.
In this case the bottom-most «Caused by» line and the adjacent blue file link copied together looks like:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method
'java.lang.String android.content.Intent.getStringExtra(java.lang.String)'
on a null object reference
at com.codepath.flixster.VideoActivity.<init>(VideoActivity.java:13)
Note that the top part of the stack trace block above that line noting FATAL EXCEPTION and the first portion with the java.lang.RuntimeException are much more generic errors and are less useful than that final bottom-most «Caused by» exception captured above which points to the real culprit.
Identify the Exception
Next, we need to identify the actual issue that this stack trace is warning us about. To do this, we need to identify the following elements of the problem:
- File name
- Line number
- Exception type
- Exception message
Consider the exception we copied above:
This exception needs to be translated to identifying the following elements:
| Element | Identified culprit |
|---|---|
| Filename | VideoActivity.java |
| Line Number | Line 13 |
| Exception Type | java.lang.NullPointerException |
| Exception Message | Attempt to invoke virtual method 'java.lang.String android.content.Intent.getStringExtra(java.lang.String)' on a null object reference |
Open the Offending File
Next, we need to open up the offending file and line number. In this case, the VideoActivity.java to line 13 which looks like this:
public class VideoActivity extends YouTubeBaseActivity{ // -----> LINE 13 is immediately below <-------- String videoKey = getIntent().getStringExtra("video_key"); // ... protected void onCreate(Bundle savedInstanceState) { // .... } }
Therefore, we know the crash is happening on line 13:
String videoKey = getIntent().getStringExtra("video_key");
This is a great first step to now solving the problem because we know exactly where the exception is being triggered.
Diagnose the Problem
Next, we need to use the exception type and message to diagnose the problem at that line. The type in this case is java.lang.NullPointerException and the message was Attempt to invoke virtual method 'java.lang.String android.content.Intent.getStringExtra(java.lang.String)' on a null object reference.
The type is the most important part because there are a limited number of types. If the type is java.lang.NullPointerException then we know that some object is null when it shouldn’t be. In other words, we are calling a method on an object or passing an object that is null (has no value). This usually means we forgot to set the value or the value is being set incorrectly.
The message above gives you the specifics that the method getStringExtra is being called on a null object. This tell us that getIntent() is actually null since this is the object getStringExtra is being called on. That might seem strange, why is the getIntent() null here? How do we fix this?
Google for the Exception
Often we won’t know what is going wrong even after we’ve diagnosed the issue. We know that «getIntent() is null and shouldn’t be». But we don’t know why or how to fix.
At this stage, we need to google cleverly for the solution. Any problem you have, stackoverflow probably has the answer. We need to identify a search query that is likely to find us answers. The recipe is generally a query like android [exception type] [partial exception message]. The type in this case is java.lang.NullPointerException and the message was Attempt to invoke virtual method 'java.lang.String android.content.Intent.getStringExtra(java.lang.String)' on a null object reference.
We might start by googling: android java.lang.NullPointerException android.content.Intent.getStringExtra(java.lang.String). These results get returned.
You generally want to look for «stackoverflow» links but in this case, the first result is this other forum.
Scroll to the bottom and you will find this message by the original author:
I realize my mistake now. I was calling the getIntent() method outside of the onCreate method.
As soon as I moved the code calling getIntent() inside the onCreate method it’s all working fine.
Let’s give this solution a try! He seems to suggest the issue is that getIntent() is being called outside of the onCreate block and we need to move it inside that method.
Address the Problem
Based on the advice given on stackoverflow or other sites from Google, we can then apply a potential fix. In this case, the VideoActivity.java can then be changed to:
public class VideoActivity extends YouTubeBaseActivity{ // ...move the call into the onCreate block String videoKey; // declare here protected void onCreate(Bundle savedInstanceState) { // .... // set the value inside the block videoKey = getIntent().getStringExtra("video_key"); } }
Now, we can run the app and see if things work as expected!
Verify the Error is Fixed
Let’s re-run the app and try this out again:
Great! The exception seems to have been fixed!
Rinse and Repeat
Sometimes the first fix we try after googling doesn’t work. Or it makes things worse. Sometimes the solutions are wrong. Sometimes we have to try different queries. Here’s a few guidelines:
- It’s normal to have to try 3-4 solutions before one actually works.
- Try to stick with stackoverflow results from Google first
- Open up multiple stackoverflow pages and look for answers you see repeated multiple times
- Look for stackoverflow answers that have a green check mark and many upvotes.
Don’t get discouraged! Look through a handful of sites and you are bound to find the solution in 80% of cases as long as you have the right query. Try multiple variations of your query until you find the right solution.
In certain cases, we need to investigate the problem further. The methods for investigating why something is broken are outlined below.
Investigation Methodologies
In addition to finding and googling errors, often additional methods have to be applied to figure out what is going wrong. This usually becomes helpful when something isn’t as we expected in the app and we need to figure out why. The three most common investigation techniques in Android are:
- Toasts — Using toasts to alert us to failures
- Logging — Using the logging system to print out values
- Breakpoints — Using the breakpoint system to investigate values
Both methods are intended for us to be able to determine why a value isn’t as we expect. Let’s take a look at how to use each.
Let’s start with the same app from above and suppose we are trying to get the app so that when the image at the top of the movie details page is clicked, then the movie trailer begins playing.
public class InfoActivity extends YouTubeBaseActivity { private ImageView backdropTrailer; private String videoKey; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_info); // ... backdropTrailer = (ImageView) findViewById(R.id.ivPoster); // Trigger video when image is clicked backdropTrailer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Launch youtube video with key if not null if (videoKey != null) { Intent i = new Intent(InfoActivity.this, VideoActivity.class); i.putExtra("videoKey", videoKey); startActivity(i); } } }); } }
Unfortunately when testing, we see that the trailer does not come up as expected when we run the app and click the top image:
Why doesn’t the video launch when you click on the image as expected? Let’s investigate.
Notifying Failure with Toasts
The video activity isn’t launching when the user presses the image but let’s see if we can narrow down the problem. First, let’s add a toast message to make sure we can begin to understand the issue. Toasts are messages that popup within the app. For example:
Toast.makeText(this, "Message saved as draft.", Toast.LENGTH_SHORT).show();
would produce:
In InfoActivity.java, we will add the following toast message inside the onClick listener method:
public class InfoActivity extends YouTubeBaseActivity { // ... protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_info); // ... backdropTrailer = (ImageView) findViewById(R.id.ivPoster); backdropTrailer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Launch youtube video with key if not null if (videoKey != null) { Intent i = new Intent(InfoActivity.this, VideoActivity.class); i.putExtra("videoKey", videoKey); startActivity(i); } else { // ----> ADD A TOAST HERE. This means video key is null Toast.makeText(InfoActivity.this, "The key is null!", Toast.LENGTH_SHORT).show(); } } }); } }
With the toast added, running this again, we see the problem is confirmed:
The problem is that the youtube video key is null where as it should have a value. We haven’t fixed the problem, but we at least know what the initial issue is.
Using a toast message we can easily notify ourselves when something goes wrong in our app. We don’t even have to check the logs. Be sure to add toasts whenever there are failure cases for networking, intents, persistence, etc so you can easily spot when things go wrong.
Investigating using Logging
Next, let’s fix the problem the toast clarified for us. We now know the problem is that the youtube video key is null where as it should have a value. Let’s take a look at the method that fetches the video key for us:
public void fetchMovies(int videoId) { // URL should be: https://api.themoviedb.org/3/movie/246655/videos?api_key=KEY String url = "https://api.themoviedb.org/3/movie" + movie_id + "/videos?api_key=" + KEY; client.get(url, new JsonHttpResponseHandler(){ @Override public void onSuccess(int statusCode, Headers headers, JSON response) { JSONArray movieJsonResults = null; try { movieJsonResults = response.getJSONArray("results"); JSONObject result = movieJsonResults.getJSONObject(0); // Get the key from the JSON videoKey = result.getString("key"); } catch (JSONException e) { e.printStackTrace(); } } }); }
This method is somehow not fetching the key that we’d expect. Let’s start by logging inside the onSuccess method to see if we are getting inside there as we expect.
The Android logger is pretty easy to use. The log options are as follows:
| Level | Method |
|---|---|
| Verbose | Log.v() |
| Debug | Log.d() |
| Info | Log.i() |
| Warn | Log.w() |
| Error | Log.e() |
They are sorted by relevance with Log.i() being the least important one. The first parameter of these methods is the category string (can be any label we choose) and the second is the actual message to display.
We can use the logger by adding two lines to the code: one at the top before the network call and one inside onSuccess to see if they both display:
Log.e("VIDEOS", "HELLO"); // <------------ LOGGING client.get(url, new JsonHttpResponseHandler(){ @Override public void onSuccess(int statusCode, Headers headers, JSON response) { JSONArray movieJsonResults = null; try { // LOG the entire JSON response string below Log.e("VIDEOS", response.toString()); // <------------ LOGGING movieJsonResults = response.getJSONArray("results"); JSONObject result = movieJsonResults.getJSONObject(0); videoKey = result.getString("key"); } catch (JSONException e) { e.printStackTrace(); } } });
When running the app, the first log does show up as expected but the one inside onSuccess does not show up at all in the Android monitor:
Notice that we see HELLO but no other line logs. This means that we now know that the onSuccess method is never called. This means our network request sent out is failing for some reason. We are one step closer to fixing this issue.
Investigating using Breakpoints
In order to investigate why the network request sent out is failing, we are going to bring out the most powerful debugging tool we have which is the breakpointing engine in Android Studio which allows us to stop the app and investigate our environment thoroughly.
First, we have to decide at which points we want to stop the app and investigate. This is done by setting breakpoints. Let’s set two breakpoints to investigate the network request:
Now, we need to run the app using the «debugger» rather than the normal run command:
Once the debugger connects, we can click on the movie to trigger the code to run. Once the code hits the spot with a breakpoint, the entire code pauses and let’s us inspect everything:
Here we were able to inspect the URL and compare the URL against the expected value. Our actual URL was «https://api.themoviedb.org/3/movie246655/videos?api_key=KEY» while the expected URL was «https://api.themoviedb.org/3/movie/246655/videos?api_key=KEY». Extremely subtle difference. Can you spot it?
Then we can hit «resume» () to continue until the next breakpoint or stop debugging (
) to end the session.
Breakpoints are incredibly powerful and worthy of additional investigation. To learn more about breakpoints, check out this official Android Guide on debugging and this third-party breakpoints guide.
Fixing the Issue
Now that we know the issue is the URL being incorrectly formed, we can fix that in the original code in InfoActivity.java:
public void fetchMovies(int videoId) { // ADD trailing slash to the URL to fix the issue String url = "https://api.themoviedb.org/3/movie/" + // <----- add trailing slash movie_id + "/videos?api_key=" + KEY; client.get(url, new JsonHttpResponseHandler(){ // ...same as before... }); }
and remove any old log statements we don’t need. Now, we can try running the app again:
Great! The video now plays exactly as expected!
Wrapping Up
In this section, we looked at three important ways to investigate your application:
- Toasts — Display messages inside the app emulator. Good for notifying you of common failure cases.
- Logs — Display messages in the Android monitor. Good to printing out values and seeing if code is running.
- Breakpoints — Halt execution of your code with breakpoints and inspect the entire environment to figure out which values are incorrect or what code hasn’t run.
With these tools and the power of Google, you should be well-equipped to debug any errors or issues that come up as you are developing your apps. Good luck!
References
- http://andressjsu.blogspot.com/2016/07/android-debugging.html
- https://developer.android.com/studio/debug/index.html
- http://blog.strv.com/debugging-in-android-studio-as/
- https://www.youtube.com/watch?v=2c1L19ZP5Qg
- https://docs.google.com/presentation/d/1DUigTm6Uh43vatHkB4rFkVVIt1zf7zB7Z5tpGTy2oFY/edit?usp=sharing
Overview
When building Android apps, your app is bound to crash from time to time or exhibit strange unexpected behavior. You know you have experienced a runtime exception when you see this in your emulator or device:
Don’t worry though! This is totally normal and there’s a specific set of steps you can take to solve these. Refer to our guide below and/or these debugging slides for more a detailed look at debugging crashes and investigating unexpected problems with your app.
Debugging Mindset
As an Android developer, you’ll need to cultivate a «debugging mindset» as well as build up defensive programming practices that make writing error-prone code less likely. In addition, you’ll often find yourself in the role of a coding investigator in order to understand where and why an app is crashing or not working as expected. A few key principles about debugging are captured below:
- Just because a program runs, doesn’t mean it’s going to work as you expected. This class of issues are known as runtime errors. This is contrast to compile-time errors which prevent an app from running and are often easier to catch.
- Think of debugging as an opportunity to fill gaps in knowledge. Debugging is an opportunity to understand your app better than you did before and hopefully sharpen your ability to write correct code in the future by programming defensively.
- Debugging is a vital part of the software development process. Often you may find yourself on some days spending more time debugging crashes or unexpected behavior then writing new code. This is entirely normal as a mobile engineer.
Debugging Principles
The following high-level principles should be applied when faced with an unexpected app behavior during investigation:
- Replicate. Convince yourself that there is an issue in the code that can be repeatedly reproduced by following the same steps. Before assuming the code is broken, try restarting the emulator, trying the app on a device and/or fully re-building the app.
- Reduce. Try to isolate or reduce the code surrounding the issue and figure out the simplest way to reproduce what’s occurring. Comment out or remove extraneous code that could be complicating the issue.
- Research. If you are running into a major unexpected issue, you are probably not alone. Search Google for the behavior using any descriptive identifiers. Visit the issue tracker for the component you are seeing issues with. Search stackoverflow for posts about the same issue.
Armed with this mindset and the above principles, let’s take a look at how to debug and investigate issues that arise within our apps.
Debugging Procedure
When you see your app crash and close, the basic steps for diagnosing and resolving this are outlined below:
- Find the final exception stack trace within the Android Monitor (logcat)
- Identify the exception type, message, and file with line number
- Open the file within your app and find the line number
- Look at the exception type and message to diagnose the problem
- If the problem is not familiar, google around searching for answers
- Make fixes based on the proposed solutions and re-run the app
- Repeat until the crash no longer occurs
This process nearly always starts with an unexpected app crash as we’ll see below.
Witnessing the Crash
Suppose we were building a simple movie trailer app called Flixster that lets the users browse new movies and watch trailers from Youtube. Imagine we ran the app, and we wanted to play the trailer and we saw this crash instead:
First off though when you see the crash dialog, don’t press OK on that dialog until after you’ve already went through these steps below to identify the stacktrace details.
Setting Up Error Filter
First, within Android Studio, be sure to setup your Android Monitor to filter for «Errors» only to reduce noise:
- Select «Error» as the log level to display
- Select «Show only selected application» to filter messages
This will set you up to see only serious issues as they come up.
Note: See this blog post for improving the coloring of errors or logs in LogCat and for other related tools.
Find the Stack Trace
Now let’s go into Android Studio and select open up the «Android Monitor». Expand the monitor so you can read the log messages easily.
- Scroll to the bottom of the error looking for a line that says
Caused byall the way at the bottom of the stack trace block. The «original cause» towards the bottom of the block is the important part of the error, ignore most of the rest of the stack trace above that. - Locate that bottom-most
Caused byline as well as the line that has the blue link with the name of your activity i.eVideoActivity.java:13. Copy them onto your clipboard and paste them both into a separate text file for easy review.
In this case the bottom-most «Caused by» line and the adjacent blue file link copied together looks like:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method
'java.lang.String android.content.Intent.getStringExtra(java.lang.String)'
on a null object reference
at com.codepath.flixster.VideoActivity.<init>(VideoActivity.java:13)
Note that the top part of the stack trace block above that line noting FATAL EXCEPTION and the first portion with the java.lang.RuntimeException are much more generic errors and are less useful than that final bottom-most «Caused by» exception captured above which points to the real culprit.
Identify the Exception
Next, we need to identify the actual issue that this stack trace is warning us about. To do this, we need to identify the following elements of the problem:
- File name
- Line number
- Exception type
- Exception message
Consider the exception we copied above:
This exception needs to be translated to identifying the following elements:
| Element | Identified culprit |
|---|---|
| Filename | VideoActivity.java |
| Line Number | Line 13 |
| Exception Type | java.lang.NullPointerException |
| Exception Message | Attempt to invoke virtual method 'java.lang.String android.content.Intent.getStringExtra(java.lang.String)' on a null object reference |
Open the Offending File
Next, we need to open up the offending file and line number. In this case, the VideoActivity.java to line 13 which looks like this:
public class VideoActivity extends YouTubeBaseActivity{
// -----> LINE 13 is immediately below <--------
String videoKey = getIntent().getStringExtra("video_key");
// ...
protected void onCreate(Bundle savedInstanceState) {
// ....
}
}
Therefore, we know the crash is happening on line 13:
String videoKey = getIntent().getStringExtra("video_key");
This is a great first step to now solving the problem because we know exactly where the exception is being triggered.
Diagnose the Problem
Next, we need to use the exception type and message to diagnose the problem at that line. The type in this case is java.lang.NullPointerException and the message was Attempt to invoke virtual method 'java.lang.String android.content.Intent.getStringExtra(java.lang.String)' on a null object reference.
The type is the most important part because there are a limited number of types. If the type is java.lang.NullPointerException then we know that some object is null when it shouldn’t be. In other words, we are calling a method on an object or passing an object that is null (has no value). This usually means we forgot to set the value or the value is being set incorrectly.
The message above gives you the specifics that the method getStringExtra is being called on a null object. This tell us that getIntent() is actually null since this is the object getStringExtra is being called on. That might seem strange, why is the getIntent() null here? How do we fix this?
Google for the Exception
Often we won’t know what is going wrong even after we’ve diagnosed the issue. We know that «getIntent() is null and shouldn’t be». But we don’t know why or how to fix.
At this stage, we need to google cleverly for the solution. Any problem you have, stackoverflow probably has the answer. We need to identify a search query that is likely to find us answers. The recipe is generally a query like android [exception type] [partial exception message]. The type in this case is java.lang.NullPointerException and the message was Attempt to invoke virtual method 'java.lang.String android.content.Intent.getStringExtra(java.lang.String)' on a null object reference.
We might start by googling: android java.lang.NullPointerException android.content.Intent.getStringExtra(java.lang.String). These results get returned.
You generally want to look for «stackoverflow» links but in this case, the first result is this other forum.
Scroll to the bottom and you will find this message by the original author:
I realize my mistake now. I was calling the getIntent() method outside of the onCreate method.
As soon as I moved the code calling getIntent() inside the onCreate method it’s all working fine.
Let’s give this solution a try! He seems to suggest the issue is that getIntent() is being called outside of the onCreate block and we need to move it inside that method.
Address the Problem
Based on the advice given on stackoverflow or other sites from Google, we can then apply a potential fix. In this case, the VideoActivity.java can then be changed to:
public class VideoActivity extends YouTubeBaseActivity{
// ...move the call into the onCreate block
String videoKey; // declare here
protected void onCreate(Bundle savedInstanceState) {
// ....
// set the value inside the block
videoKey = getIntent().getStringExtra("video_key");
}
}
Now, we can run the app and see if things work as expected!
Verify the Error is Fixed
Let’s re-run the app and try this out again:
Great! The exception seems to have been fixed!
Rinse and Repeat
Sometimes the first fix we try after googling doesn’t work. Or it makes things worse. Sometimes the solutions are wrong. Sometimes we have to try different queries. Here’s a few guidelines:
- It’s normal to have to try 3-4 solutions before one actually works.
- Try to stick with stackoverflow results from Google first
- Open up multiple stackoverflow pages and look for answers you see repeated multiple times
- Look for stackoverflow answers that have a green check mark and many upvotes.
Don’t get discouraged! Look through a handful of sites and you are bound to find the solution in 80% of cases as long as you have the right query. Try multiple variations of your query until you find the right solution.
In certain cases, we need to investigate the problem further. The methods for investigating why something is broken are outlined below.
Investigation Methodologies
In addition to finding and googling errors, often additional methods have to be applied to figure out what is going wrong. This usually becomes helpful when something isn’t as we expected in the app and we need to figure out why. The three most common investigation techniques in Android are:
- Toasts — Using toasts to alert us to failures
- Logging — Using the logging system to print out values
- Breakpoints — Using the breakpoint system to investigate values
Both methods are intended for us to be able to determine why a value isn’t as we expect. Let’s take a look at how to use each.
Let’s start with the same app from above and suppose we are trying to get the app so that when the image at the top of the movie details page is clicked, then the movie trailer begins playing.
public class InfoActivity extends YouTubeBaseActivity {
private ImageView backdropTrailer;
private String videoKey;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_info);
// ...
backdropTrailer = (ImageView) findViewById(R.id.ivPoster);
// Trigger video when image is clicked
backdropTrailer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Launch youtube video with key if not null
if (videoKey != null) {
Intent i = new Intent(InfoActivity.this, VideoActivity.class);
i.putExtra("videoKey", videoKey);
startActivity(i);
}
}
});
}
}
Unfortunately when testing, we see that the trailer does not come up as expected when we run the app and click the top image:
Why doesn’t the video launch when you click on the image as expected? Let’s investigate.
Notifying Failure with Toasts
The video activity isn’t launching when the user presses the image but let’s see if we can narrow down the problem. First, let’s add a toast message to make sure we can begin to understand the issue. Toasts are messages that popup within the app. For example:
Toast.makeText(this, "Message saved as draft.", Toast.LENGTH_SHORT).show();
would produce:
In InfoActivity.java, we will add the following toast message inside the onClick listener method:
public class InfoActivity extends YouTubeBaseActivity {
// ...
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_info);
// ...
backdropTrailer = (ImageView) findViewById(R.id.ivPoster);
backdropTrailer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Launch youtube video with key if not null
if (videoKey != null) {
Intent i = new Intent(InfoActivity.this, VideoActivity.class);
i.putExtra("videoKey", videoKey);
startActivity(i);
} else {
// ----> ADD A TOAST HERE. This means video key is null
Toast.makeText(InfoActivity.this, "The key is null!", Toast.LENGTH_SHORT).show();
}
}
});
}
}
With the toast added, running this again, we see the problem is confirmed:
The problem is that the youtube video key is null where as it should have a value. We haven’t fixed the problem, but we at least know what the initial issue is.
Using a toast message we can easily notify ourselves when something goes wrong in our app. We don’t even have to check the logs. Be sure to add toasts whenever there are failure cases for networking, intents, persistence, etc so you can easily spot when things go wrong.
Investigating using Logging
Next, let’s fix the problem the toast clarified for us. We now know the problem is that the youtube video key is null where as it should have a value. Let’s take a look at the method that fetches the video key for us:
public void fetchMovies(int videoId) {
// URL should be: https://api.themoviedb.org/3/movie/246655/videos?api_key=KEY
String url = "https://api.themoviedb.org/3/movie" + movie_id + "/videos?api_key=" + KEY;
client.get(url, new JsonHttpResponseHandler(){
@Override
public void onSuccess(int statusCode, Headers headers, JSON response) {
JSONArray movieJsonResults = null;
try {
movieJsonResults = response.getJSONArray("results");
JSONObject result = movieJsonResults.getJSONObject(0);
// Get the key from the JSON
videoKey = result.getString("key");
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
This method is somehow not fetching the key that we’d expect. Let’s start by logging inside the onSuccess method to see if we are getting inside there as we expect.
The Android logger is pretty easy to use. The log options are as follows:
| Level | Method |
|---|---|
| Verbose | Log.v() |
| Debug | Log.d() |
| Info | Log.i() |
| Warn | Log.w() |
| Error | Log.e() |
They are sorted by relevance with Log.i() being the least important one. The first parameter of these methods is the category string (can be any label we choose) and the second is the actual message to display.
We can use the logger by adding two lines to the code: one at the top before the network call and one inside onSuccess to see if they both display:
Log.e("VIDEOS", "HELLO"); // <------------ LOGGING
client.get(url, new JsonHttpResponseHandler(){
@Override
public void onSuccess(int statusCode, Headers headers, JSON response) {
JSONArray movieJsonResults = null;
try {
// LOG the entire JSON response string below
Log.e("VIDEOS", response.toString()); // <------------ LOGGING
movieJsonResults = response.getJSONArray("results");
JSONObject result = movieJsonResults.getJSONObject(0);
videoKey = result.getString("key");
} catch (JSONException e) {
e.printStackTrace();
}
}
});
When running the app, the first log does show up as expected but the one inside onSuccess does not show up at all in the Android monitor:
Notice that we see HELLO but no other line logs. This means that we now know that the onSuccess method is never called. This means our network request sent out is failing for some reason. We are one step closer to fixing this issue.
Investigating using Breakpoints
In order to investigate why the network request sent out is failing, we are going to bring out the most powerful debugging tool we have which is the breakpointing engine in Android Studio which allows us to stop the app and investigate our environment thoroughly.
First, we have to decide at which points we want to stop the app and investigate. This is done by setting breakpoints. Let’s set two breakpoints to investigate the network request:
Now, we need to run the app using the «debugger» rather than the normal run command:
Once the debugger connects, we can click on the movie to trigger the code to run. Once the code hits the spot with a breakpoint, the entire code pauses and let’s us inspect everything:
Here we were able to inspect the URL and compare the URL against the expected value. Our actual URL was «https://api.themoviedb.org/3/movie246655/videos?api_key=KEY» while the expected URL was «https://api.themoviedb.org/3/movie/246655/videos?api_key=KEY». Extremely subtle difference. Can you spot it?
Then we can hit «resume» (

Breakpoints are incredibly powerful and worthy of additional investigation. To learn more about breakpoints, check out this official Android Guide on debugging and this third-party breakpoints guide.
Fixing the Issue
Now that we know the issue is the URL being incorrectly formed, we can fix that in the original code in InfoActivity.java:
public void fetchMovies(int videoId) {
// ADD trailing slash to the URL to fix the issue
String url = "https://api.themoviedb.org/3/movie/" + // <----- add trailing slash
movie_id + "/videos?api_key=" + KEY;
client.get(url, new JsonHttpResponseHandler(){
// ...same as before...
});
}
and remove any old log statements we don’t need. Now, we can try running the app again:
Great! The video now plays exactly as expected!
Wrapping Up
In this section, we looked at three important ways to investigate your application:
- Toasts — Display messages inside the app emulator. Good for notifying you of common failure cases.
- Logs — Display messages in the Android monitor. Good to printing out values and seeing if code is running.
- Breakpoints — Halt execution of your code with breakpoints and inspect the entire environment to figure out which values are incorrect or what code hasn’t run.
With these tools and the power of Google, you should be well-equipped to debug any errors or issues that come up as you are developing your apps. Good luck!
References
- http://andressjsu.blogspot.com/2016/07/android-debugging.html
- https://developer.android.com/studio/debug/index.html
- http://blog.strv.com/debugging-in-android-studio-as/
- https://www.youtube.com/watch?v=2c1L19ZP5Qg
- https://docs.google.com/presentation/d/1DUigTm6Uh43vatHkB4rFkVVIt1zf7zB7Z5tpGTy2oFY/edit?usp=sharing














