Akhil Dad Akhil Dad - 3 months ago 203
Android Question

Internet check, where to place when using MVP, RX and Retrofit

I have went through this and this post. So I really agree with the second post that presenter should not be aware of android specific thing. So what I am thinking is putting internet check in service layer.
I am using Rx Java for making network calls, so I can either place the network check before making a service call, so this way I need to manually throw and IOException because I need to show an error page on view when network is not available, the other option is I create my own error class for no internet

Observable<PaginationResponse<Notification>> response = Observable.create(new Observable.OnSubscribe<PaginationResponse<Notification>>() {
@Override
public void call(Subscriber<? super PaginationResponse<Notification>> subscriber) {
if (isNetworkConnected()) {
Call<List<Notification>> call = mService.getNotifications();
try {
Response<List<Notification>> response = call.execute();
processPaginationResponse(subscriber, response);
} catch (IOException e) {
e.printStackTrace();
subscriber.onError(e);
}
} else {
//This is I am adding manually
subscriber.onError(new IOException);
}
subscriber.onCompleted();
}
});


The other way I though of is adding interceptor to OkHttpClient and set it to retrofit

OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
if (!isNetworkConnected()) {
throw new IOException();
}
final Request.Builder builder = chain.request().newBuilder();

Request request = builder.build();

return chain.proceed(request);
}
});


Now the 2nd approach is more scalable, but I am not sure it will be efficient as I would be unnecessarily calling service method and call.execute() method.

Any suggestion which way should be used?
Also my parameter for judging the way is


  • Efficiency

  • Scalability

  • Generic : I want this same logic can be used across apps who are following the similar architecture where MVP and Repository/DataProvider (May give data from network/db)



Other suggestions are also welcome, if you people are already using any other way.

Answer

First we create a utility for checking internet connection, there are two ways we can create this utility, one where the utility emits the status only once, which looks like this,

public class InternetConnection {
    public static Observable<Boolean> isInternetOn(Context context) {
        ConnectivityManager connectivityManager
                = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return Observable.just(activeNetworkInfo != null && activeNetworkInfo.isConnected());
    }
}

Other way of creating this utility is, where the utility keeps emitting the connection status if it changes, which looks like this,

public class InternetConnection {
    public Observable<Boolean> isInternetOn(Context context) {
        final IntentFilter filter = new IntentFilter();
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);

        return Observable.create(new Observable.OnSubscribe<Boolean>() {
            @Override
            public void call(final Subscriber<? super Boolean> subscriber) {
                final BroadcastReceiver receiver = new BroadcastReceiver() {
                    @Override
                    public void onReceive(Context context, Intent intent) {
                        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
                        NetworkInfo netInfo = cm.getActiveNetworkInfo();
                        subscriber.onNext(netInfo != null && netInfo.isConnected());
                    }
                };

                context.registerReceiver(receiver, filter);

                subscriber.add(unsubscribeInUiThread(() -> context.unregisterReceiver(receiver)));
            }
        }).defaultIfEmpty(false);
    }

    private Subscription unsubscribeInUiThread(final Action0 unsubscribe) {
        return Subscriptions.create(() -> {
            if (Looper.getMainLooper() == Looper.myLooper()) {
                unsubscribe.call();
            } else {
                final Scheduler.Worker inner = AndroidSchedulers.mainThread().createWorker();
                inner.schedule(() -> {
                    unsubscribe.call();
                    inner.unsubscribe();
                });
            }
        });
    }
}

Next, in your dataSource or Presenter use switchMap or flatMap to check for internet connection before doing any network operation which looks like this,

private Observable<List<GitHubUser>> getGitHubUsersFromRetrofit() {
    return isInternetOn(context)
            .filter(connectionStatus -> connectionStatus)
            .switchMap(connectionStatus -> gitHubApiInterface.getGitHubUsersList()
                    .map(gitHubUserList -> {
                       gitHubUserDao.storeOrUpdateGitHubUserList(gitHubUserList);
                        return gitHubUserList;
                    }));
}

Note that, we are using switchMap instead of flatMap. why switchMap? because, we have 2 data stream here, first is internet connection and second is Retrofit. first we will take connection status value (true/false), if we have active connection, we will create a new Retrofit stream and return start getting results, down the line if we the status of the connection changes, switchMap will first stop the existing Retrofit connection and then decide if we need to start a new one or ignore it.

EDIT: This is one of the sample, which might give better clarity https://github.com/viraj49/Realm_android-injection-rx-test/blob/master/app-safeIntegration/src/main/java/tank/viraj/realm/dataSource/GitHubUserListDataSource.java

EDIT2:

So you mean switch map will try it itself once internet is back?

Yes and No, let's first see the difference between flatMap & switchMap. Let's say we have an editText and we search some info from network based on what user types, every time user adds a new character we have to make a new query (which can be reduced with debounce), now with so many network calls only the latest results are useful, with flatMap we will receive all the results from all the calls we made to the network, with switchMap on the other hand, the moment we make a query, all previous calls are discarded.

Now the solution here is made of 2 parts,

  1. We need an Observable that keeps emitting current state of Network, the first InternetConnection above sends the status once and calls onComplete(), but the second one has a Broadcast receiver and it will keep sending onNext() when network status changes. IF you need to make a reactive solution go for case-2

  2. Let's say you choose InternetConnection case-2, in this case use switchMap(), cause when network status changes, we need to stop Retrofit from whatever it is doing and then based on the status of network either make a new call or don't make a call.

How do I let my view know that the error is internet one also will this be scalable because I need to do with every network call, any suggestions regarding writing a wrapper?

Writing a wrapper would be excellent choice, you can create your own custom response which can take multiple entries from a set of possible responses e.g. SUCCESS_INTERNET, SUCCESS_LOGIN, ERROR_INVALID_ID

EDIT3: Please find an updated InternetConnectionUtil here https://github.com/viraj49/Realm_android-injection-rx-test/blob/master/app-safeIntegration/src/main/java/tank/viraj/realm/util/InternetConnection.java

More detail on the same topic is here: https://medium.com/@Viraj.Tank/android-mvp-that-survives-view-life-cycle-configuration-internet-changes-part-2-6b1e2b5c5294

Comments