Networking is part of probably 99 percent of mobile apps nowadays: we always need to connect to a remote server to retrieve the information we need in our app.
As a first approach to networking, we are going to create a new scenario in which we are going to:
Our user interface will be very simple. We will just need a fancy progress bar and a DOWNLOAD button.
First of all, we will create mDownloadProgress
:
private PublishSubject<Integer>mDownloadProgress = PublishSubject.create();
This is the subject that we are going to use to manage the progress bar updates. This subject works together with the download
function:
private boolean downloadFile(String source, String destination) { boolean result = false; InputStream input = null; OutputStream output = null; HttpURLConnection connection = null; try { URL url = new URL(source); connection = (HttpURLConnection) url.openConnection(); connection.connect(); if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { return false; } int fileLength = connection.getContentLength(); input = connection.getInputStream(); output = new FileOutputStream(destination); byte data[] = new byte[4096]; long total = 0; int count; while ((count = input.read(data)) != -1) { total += count; if (fileLength >0) { int percentage = (int) (total * 100 / fileLength); mDownloadProgress.onNext(percentage); } output.write(data, 0, count); } mDownloadProgress.onCompleted(); result = true; } catch (Exception e) { mDownloadProgress.onError(e); } finally { try { if (output != null) { output.close(); } if (input != null) { input.close(); } } catch (IOException e) { mDownloadProgress.onError(e); } if (connection != null) { connection.disconnect(); mDownloadProgress.onCompleted(); } } return result; }
Using this code as it is will trigger NetworkOnMainThreadException
. We can easily create a RxJava version of this function and jump into our beloved reactive world to solve this issue:
private Observable<Boolean> obserbableDownload(String source, String destination) { return Observable.create(subscriber -> { try { boolean result = downloadFile(source, destination); if (result) { subscriber.onNext(true); subscriber.onCompleted(); } else { subscriber.onError(new Throwable("Download failed.")); } } catch (Exception e) { subscriber.onError(e); } }); }
Now we need to trigger the download, tapping the download
button:
@OnClick(R.id.button_download) void download() { mButton.setText(getString(R.string.downloading)); mButton.setClickable(false); mDownloadProgress .distinct() .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<Integer>() { @Override public void onCompleted() { App.L.debug("Completed"); } @Override public void onError(Throwable e) { App.L.error(e.toString()); } @Override public void onNext(Integer progress) { mArcProgress.setProgress(progress); } }); String destination = "/sdcard/softboy.avi"; obserbableDownload("http://archive.blender.org/fileadmin/movies/ softboy.avi", destination) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(success -> { resetDownloadButton(); Intent intent = new Intent(android.content.Intent.ACTION_VIEW); File file = new File(destination); intent.setDataAndType(Uri.fromFile(file), "video/avi"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); }, error -> { Toast.makeText(getActivity(), "Something went south", Toast.LENGTH_SHORT).show(); resetDownloadButton(); }); }
We are using the Butter Knife annotation's @OnClick
to bind the button to the method, and we are updating the button message and its clickable status: we don't want the user to trigger multiple downloads with multiple clicks.
Then, we create a new subscription to observe the download progress and update the progress bar accordingly. Obviously, we are observing on the main thread because the progress bar is a UI element:
obserbableDownload("http://archive.blender.org/fileadmin/movies/ softboy.avi", "/sdcard/softboy.avi";)
This is the download Observable. A network call is an I/O job and we are using the I/O Scheduler, as expected. When the download is complete, we are in onNext()
and we can launch the video player, knowing that the downloaded file will be available to the player at the destination URI.
The following figure shows the download progress and the video player dialog:
18.191.74.66