1

I have a content provider which performs database or network requests. If there is no network, an IOException is raised and I would like to do another request (on the database this time).

My code looks like

/**
 * Content provider
 */
//...
public Cursor query (Uri uri /*,...*/) {
    switch(uriMatcher.match(uri)) {
        case NETWORK:
            JSONObject json;
            try {
                json = new HttpTask().getItems(); // If no network throws IoException
            } catch (IOException e) {
                //Do something
            }
            return new Cursor(/* ... */);
            break;
        case DATABASE:
            //Access database
            return new Cursor(/* ... */);
            break;
    }
}
//...

Should I :

  1. Handle the exception only in the contentProvider and directly move to the database case if an error occured ?
  2. Handle the exception in the HttpTask so the json will "just" be null
  3. Create a custom cursorloader like this one and a custom RuntimeException ? If so, within the content provider I will able to throw the exception and catch it in the CursorLoader.

EDIT 1

Or should I check the network state before using :

ConnectivityManager connectivityManager = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
    NetworkInfo ni = connectivityManager.getActiveNetworkInfo();
    if (ni.getState() != NetworkInfo.State.CONNECTED) {
        // record the fact that there is not connection
        isConnected = false;
    }
}

EDIT 2 Here is how to do using a custom exception and a custom loader

NoNetworkException.java

public class NoNetworkException extends RuntimeException {
    public NoNetworkException(String s) {
        super(s);
    }
}

NoNetworkSafeCursorLoader.java

public class NoNetworkSafeCursorLoader extends CursorLoader {
    private NoNetworkException exception;

    public NoNetworkSafeCursorLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        super(context, uri, projection, selection, selectionArgs, sortOrder);
    }

    @Override
    public Cursor loadInBackground() {
        try {
            return super.loadInBackground();
        } catch(NoNetworkException exception) {
            this.exception = exception;
        }
        return null;
    }

    public NoNetworkException getException() {
        return exception;
    }
}

Now in the content provider you need to throw NoNetworkException

//...
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws NoNetworkException {
    //...
}
//...

and in the callback you must check if an exception was thrown

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) throws NoNetworkException {
    NoNetworkSafeCursorLoader l = (NoNetworkSafeCursorLoader) loader;

    if(l.getException() != null) {
        //No network, do somethings
    }
    else {
        //Network ok, do otherthings
    }
}
Community
  • 1
  • 1
ThomasThiebaud
  • 11,331
  • 6
  • 54
  • 77

3 Answers3

1

You should definitely check the network state first. Then if the network request fails do the database access in the catch-block

public Cursor query (Uri uri /*,...*/) {
    Cursor cursor;
    switch(uriMatcher.match(uri)) {
        case NETWORK:
            JSONObject json;
            try {
                 json = new HttpTask().getItems(); // If no network throws IoException
                 cursor = new Cursor(/* convert json */)
            } catch (IOException e) {
                cursor = //access the database
            }
            break;
        case DATABASE:
            //Access database
            cursor = new Cursor(/* ... */);
            break;
        }
    return cursor;
}

Also you should have a look at Sync Adapters which are the way google says you should to this. Depending on your usecase it could be a great help since Sync Adapters already handle network checking and other things to maximize battery life.

leonardkraemer
  • 6,573
  • 1
  • 31
  • 54
1

There are few thing you can take care of course in this case,

  1. Yes as @leoderprofi said, you have to check the network is available or not to the HTTP task you want.

  2. Instead of using the Try Catch to manage after the exception thrown. what you can do is check for the object availability before that. Like, check that the JSON is null before parsing it, After fetching the cursor check also that it is null or not and if not null than does it actually have any data init or not, it is most likely to get exception in cursor that cursor object is present but it does not have any data in it and when you fetch data from it Gives you exception.

  3. Yes, you can create a custom loader and handle and throw exception there, what you can also do is also create a success and error callbacks for that using interface to manage it.

Hope it helps. Thanks.

Hardik Chauhan
  • 2,750
  • 15
  • 30
1

A proper solution...encapsulate your network functionality, failover to db if network unavailable, no second query call needed.

I wouldn't create a custom loader and exception unless your client REALLY wants to know that the network attempt failed, but if its just going to follow it up with a db request, then why bother notifying anyone, but that's up to your use case.

Without knowing your actually requirements; it's cleaner to get rid of both NETWORK and DATABASE content URIs and just use one, MY_DATA. Does your client really know or care if it's requesting for either/or?

public Cursor query (Uri uri /*,...*/) {
    switch(uriMatcher.match(uri)) {
        case NETWORK:
            // check network
            if ( isNetworkAvailable(context) ) {
                JSONObject json;
                try {
                    json = new HttpTask().getItems(); // If no network throws IoException
                    return new Cursor(/* ... */);
                } catch (IOException e) {
                    // Log a warning
                }
            }
            // delete this break;
            // failover to DB
        case DATABASE:
            //Access database
            return new Cursor(/* ... */);
            break;
    } 
}

private boolean isNetworkAvailable(Context ctx) {
    boolean isConnected = false;
    ConnectivityManager connectivityManager = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
    if (connectivityManager != null)
        NetworkInfo ni = connectivityManager.getActiveNetworkInfo();
        isConnected = ni.getState() == NetworkInfo.State.CONNECTED;
    }
    return isConnected;
}

You could even make public static boolean isNetworkAvailable(...) for an easy utility method you can use elsewhere.

TonyB
  • 142
  • 1
  • So if I want to return a message to the user (basically "No network, do something else"), create a custom exception sounds reasonable. – ThomasThiebaud Oct 31 '15 at 20:57
  • Yea, I *think* you can create and throw a custom runtime exception if you want too. – TonyB Nov 01 '15 at 18:21