Обработка ошибок android

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

If my app crashes, it hangs for a couple of seconds before I’m told by Android that the app crashed and needs to close. So I was thinking of catching all exceptions in my app with a general:

try {
    // ... 
} catch(Exception e) { 
    // ...
} 

And make a new Activity that explains that the application crashed instantly (and also giving users an opportunity to send a mail with the error details), instead of having that delay thanks to Android. Are there better methods of accomplishing this or is this discouraged?

Update: I am using a Nexus 5 with ART enabled and I am not noticing the delay I used to experience with apps crashing (the «hanging» I was talking about originally). I think since everything is native code now, the crash happens instantly along with getting all the crash information. Perhaps the Nexus 5 is just quick :) regardless, this may not be a worry in future releases of Android (given that ART is going to be the default runtime in Android L).

asked May 15, 2013 at 9:42

ldam's user avatar

ldamldam

4,4226 gold badges45 silver badges76 bronze badges

5

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 overriden onCreate method.

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 ERROR\n" +
      crashInfo.stackTrace;
  }

  private String getDeviceInfo() {
    return
      "\nDEVICE INFORMATION\n" +
        "Brand: " +
        Build.BRAND +
        "\n" +
        "Device: " +
        Build.DEVICE +
        "\n" +
        "Model: " +
        Build.MODEL +
        "\n" +
        "Id: " +
        Build.ID +
        "\n" +
        "Product: " +
        Build.PRODUCT +
        "\n";
  }

  private String getFirmwareInfo() {
    return "\nFIRMWARE\n" +
      "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());
  }
}

Mohamad Ghaith Alzin's user avatar

answered May 15, 2013 at 10:34

CRUSADER's user avatar

CRUSADERCRUSADER

5,4863 gold badges28 silver badges64 bronze badges

6

You could just use a generic alert dialog to quickly display error messages.
For example…

//******************************************
//some generic method
//******************************************
private void doStuff()
{       
    try
    {
        //do some stuff here
    }
    catch(Exception e)
    {
        messageBox("doStuff", e.getMessage());
    }
}


//*********************************************************
//generic dialog, takes in the method name and error message
//*********************************************************
private void messageBox(String method, String message)
{
    Log.d("EXCEPTION: " + method,  message);

    AlertDialog.Builder messageBox = new AlertDialog.Builder(this);
    messageBox.setTitle(method);
    messageBox.setMessage(message);
    messageBox.setCancelable(false);
    messageBox.setNeutralButton("OK", null);
    messageBox.show();
}

You could also add other error handling options into this method, such as print stacktrace

answered Aug 9, 2013 at 9:40

Louis Evans's user avatar

Louis EvansLouis Evans

6712 gold badges8 silver badges18 bronze badges

1

i found the «wtf» (what a terrible failure) method in the Log class. From the description:

Depending on system configuration, a report may be added to the
DropBoxManager and/or the process may be terminated immediately with
an error dialog.

http://developer.android.com/reference/android/util/Log.html

mightyWOZ's user avatar

mightyWOZ

7,9663 gold badges29 silver badges46 bronze badges

answered Mar 16, 2014 at 15:19

sunyata's user avatar

sunyatasunyata

1,8435 gold badges27 silver badges41 bronze badges

Обработка ошибок в Android Java

Ошибка – это неизбежная часть процесса разработки программного обеспечения. В приложениях Android Java нельзя избежать ошибок, но важно знать, как обрабатывать их, чтобы создать качественное и отзывчивое пользовательское взаимодействие. В этой статье мы рассмотрим различные способы обработки ошибок в Android Java и подробно рассмотрим концепцию и использование исключений.

Что такое исключение?

Исключение – это объект, который создается во время выполнения программы и сигнализирует об ошибке. Если в программе происходит ошибка, то она может сгенерировать исключение. Исключение может быть выброшено в любой момент выполнения программы и может быть поймано другими блоками кода.

Как поймать исключение?

Исключения в Java могут быть пойманы с помощью блока try-catch. Блок try содержит код, в котором может произойти исключение, а блок catch позволяет обработать это исключение.

Пример:

try {
// код, в котором может произойти исключение
} catch (Exception e) {
// код для обработки исключения
}

Блок catch может обрабатывать конкретные типы исключений или их родительские классы. Если будет сгенерировано исключение, которое не соответствует ни одному типу, указанному в блоках catch, оно будет передано выше по стеку вызовов для дальнейшей обработки.

Но перед тем, как обработать исключение, важно знать, какие исключения могут быть сгенерированы в Android Java.

Типы исключений в Android Java:

1. NullPointerException – возникает, если переменная ссылается на объект, который не был инициализирован.

2. IllegalArgumentException – возникает, если метод получает некорректное значение аргумента.

3. IllegalStateException – возникает, если состояние объекта не позволяет выполнить операцию.

4. IOException – возникает, когда возникают ошибки ввода-вывода.

5. NetworkOnMainThreadException – возникает, если сетевые операции выполняются в основном потоке (UI потоке).

6. SQLiteException – возникает, когда возникают ошибки в базе данных SQLite.

Обработка исключений в Android:

1. Вывод исключения в логи:

Когда происходит ошибка, мы можем вывести информацию об исключении в логи для дальнейшего анализа и исправления. В Android Studio это можно сделать с помощью класса Log.

Пример:

try {
// код, в котором может произойти исключение
} catch (Exception e) {
Log.e(TAG, «Ошибка: » + e.getMessage());
}

2. Отображение пользователю сообщения об ошибке:

Если исключение произошло на этапе пользовательского взаимодействия, полезно отобразить сообщение об ошибке пользователю. Для этого используйте объект класса Toast или Snackbar.

Пример:

try {
// код, в котором может произойти исключение
} catch (Exception e) {
Toast.makeText(this, «Произошла ошибка: » + e.getMessage(), Toast.LENGTH_SHORT).show();
}

3. Обработка исключений в фоновом потоке:

Если исключение возникает в фоновом потоке, оно не может быть поймано в главном UI потоке. В этом случае, мы можем использовать интерфейсы обратного вызова (callback) или использовать библиотеки, такие как RxJava или AsyncTask, чтобы вернуть исключение из фонового потока и обработать его в главном потоке.

Пример:

public interface OnErrorListener {
void onError(Exception e);
}

public class MyTask implements Runnable {
private OnErrorListener onErrorListener;

public MyTask(OnErrorListener onErrorListener) {
this.onErrorListener = onErrorListener;
}

@Override
public void run() {
try {
// код, в котором может произойти исключение
} catch (Exception e) {
if (onErrorListener != null) {
onErrorListener.onError(e);
}
}
}
}

// Запуск задачи
MyTask myTask = new MyTask(new OnErrorListener() {
@Override
public void onError(Exception e) {
// обработка исключения в главном потоке
}
});
new Thread(myTask).start();

Это лишь некоторые способы обработки исключений в Android Java. В каждом конкретном случае важно выбрать подходящий метод для вашего проекта. Обратите внимание, что обработка ошибок – это важная часть процесса разработки программного обеспечения, и она должна быть предусмотрена при проектировании приложения.

В заключение, хорошо структурированный и эффективный код обрабатывает ошибки безопасно и с избеганием нежелательных сбоев приложения. Корректная обработка исключений в Android Java гарантирует более стабильное и надежное пользовательское взаимодействие.

Позвольте, если у вас есть вопросы или комментарии по этой статье!

Время на прочтение
3 мин

Количество просмотров 26K

При разработке приложения неизбежно приходится сталкиваться с ошибками в коде и/или окружении. И очень печально когда подобные ошибки встречаются не на тестовом телефоне/эмуляторе а у живых пользователей. Еще печальнее если это не ваш друг бета-тестер и толком никто не может объяснить что и где свалилось.

Обычно при внезапном падении приложения Android предлагает отправить отчет об ошибке, где будет и подробный стэк-трейс и информация о версии вашего приложения. К сожалению пользователи не всегда нажимают кнопку «отправить отчет» а для дебаг-приложений или приложений не из маркета такая функциональность и вовсе недоступна.

Что же делать? На помощь приедет возможность языка Java обрабатывать исключения (Exceptions), в том числе и непойманные (unhandled).

Класс Thread имеет статический метод setDefaultUncaughtExceptionHandler. Данный метод позволяет установить собственный класс-обработчик непойманных исключений. Класс-обработчик должен имплементировать интерфейс Thread.UncaughtExceptionHandler. Каркас обработчика может выглядеть примерно так:

public class TryMe implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        Log.d("TryMe", "Something wrong happened!");
    }
}

Единственный метод принимает на вход Thread — поток, в котором произошло исключение, и Throwable — само исключение. Приведенная выше реализация просто выводит в лог сообщение без каких либо деталей… Попробуем воспользоваться…

public class MainActivity extends MapActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Thread.setDefaultUncaughtExceptionHandler(new TryMe());

        Integer a=1;
        if(true)
            a=null;
        int x = 6;
        x=x/a;  // Exception here!
    }
}

После запуска вышеприведенного кода мы (ура!) получим сообщение в логе… и черный экран. Установив наш собственный обработчик мы удалил штатный обработчик ОС Android и теперь нам больше не предлагают закрыть приложение.

Исправим положение

public class TryMe implements Thread.UncaughtExceptionHandler {

    Thread.UncaughtExceptionHandler oldHandler;

    public TryMe() {
        oldHandler = Thread.getDefaultUncaughtExceptionHandler(); // сохраним ранее установленный обработчик
    }

    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        Log.d("TryMe", "Something wrong happened!");
        if(oldHandler != null) // если есть ранее установленный...
            oldHandler.uncaughtException(thread, throwable); // ...вызовем его
    }
}

Теперь мы видим и сообщение в логе, и привычное системное сообщение.

Неудобно устанавливать обработчик в Activity. Хоть он и будет установлен а все потоки, но Activity может быть несколько и несколько же стартовых. А еще могут быть сервисы… В этом случае лучше всего устанавливать обработчик при инициализации приложения. Примерно вот так:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        Thread.setDefaultUncaughtExceptionHandler(new TryMe());
        super.onCreate();
    }
}

При этом нужно не забыть прописать новый класс приложения в манифест. Примерно вот так:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="my.package">
    <application
        android:name="MyApplication" ...

Теперь при старте приложения (не важно какого его компонента) будет установлен обработчик исключений.

Конечно выводить сообщение в лог это не серьезно. Нужно собирать больше информации. Какая версия приложения? Какое исключение не обработано? Какое другое исключение привело к выбросу фатального? В каком потоке? Какой был стэк? Всю эту информацию можно получить. Код простейшего обработчика исключений получающий и сохраняющий на SD-карту всю вышеуказанную информацию размещен на GitHub.

Приведенная реализация сохраняет информацию об необработанном исключении в файл на SD-карте в папку /Android/data/your.app.package.name/files/ (так велит Dev Guide) в файлах вида stacktrace-dd-MM-yy.txt. Для работы в манифесте приложения требуется разрешение WRITE_EXTERNAL_STORAGE.

Естественно это не единственное подобное решение.

Flurry — аналитика для мобильных приложений, содержит свой обработчик ошибок. ACRA — библиотека для Android, собирает данные об ошибках и постит их на GoogleDocs. Android-remote-stacktrace — аналогичная библиотека, шлет данные на пользовательский скрипт-приемник. Также много полезного можно получить в этом вопросе на StackOverflow

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:

  1. Find the final exception stack trace within the Android Monitor (logcat)
  2. Identify the exception type, message, and file with line number
  3. Open the file within your app and find the line number
  4. Look at the exception type and message to diagnose the problem
  5. If the problem is not familiar, google around searching for answers
  6. Make fixes based on the proposed solutions and re-run the app
  7. 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:

  1. Select «Error» as the log level to display
  2. 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.

  1. Scroll to the bottom of the error looking for a line that says Caused by all 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.
  2. Locate that bottom-most Caused by line as well as the line that has the blue link with the name of your activity i.e VideoActivity.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:

  1. File name
  2. Line number
  3. Exception type
  4. 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:

  1. Toasts — Using toasts to alert us to failures
  2. Logging — Using the logging system to print out values
  3. 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:

Toast

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:

  1. Toasts — Display messages inside the app emulator. Good for notifying you of common failure cases.
  2. Logs — Display messages in the Android monitor. Good to printing out values and seeing if code is running.
  3. 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

Понравилась статья? Поделить с друзьями:
  • Оборонительный рубеж лексическая ошибка
  • Обозначение ошибок на панели приборов фольксваген тигуан
  • Обработка ошибки отсутствия страницы
  • Оболочкаactivedocument ошибка при вызове метода контекста получить
  • Обработка ошибки python valueerror