Enhancing the React Native Webview (Part 2) – Supporting File Downloads in Android

This is a continuation of part 1 and in this part we will focus on adding support to download image files using the webview in React native for Android platform. The code mentioned in this article could be enhanced a little further to support any type of file downloads.

First lets start by updating android -> app -> src -> main -> java -> com -> yourpackage-> AdvancedWebView.java file

...
import android.webkit.DownloadListener;
...

public class AdvancedWebviewManager extends ReactWebViewManager {

    ...

    @ReactProp(name = "enabledDownloadAndroid")
    public void enabledDownloadAndroid(WebView view, boolean enabled) {
        if(enabled) {
            final AdvancedWebviewModule module = this.aPackage.getModule();
            view.setDownloadListener(new DownloadListener() {

                @Override
                public void onDownloadStart(final String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
                    module.downUrl = url;
                    if(module.grantStoragePermission()) {
                        module.downloadImage(url);
                    }else{

                    }
                }
            });
        }
    }

    ...

}

enabledDownloadAndroid is a prop passed from the Javascript side. When passed true enabledDownloadAndroid method will be called and a download listener will be attached to the webview.  This listener will fire onDownloadStart when a download is triggered from the webview.  To perform a file a download only WRITE_EXTERNAL_STORAGE permission is required. So grantStoragePermission method is called which will ask for run time permissions if the android version is 6.0 or above. If the required permissions are granted it will call downloadImage method which is defined in the AdvancedWebViewModule.java file.

Next update the AdvancedWebViewModule.java with the following code

...

import okhttp3.Call;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
...

public class AdvancedWebviewModule extends ReactContextBaseJavaModule implements ActivityEventListener {
    ...
    private final int NOTIFICATION_ID = 1;
    public String downUrl = null;
    private String[] permissions = {Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE};
    ...
    private PermissionListener listener = new PermissionListener() {
        @Override
        public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
            switch (requestCode) {
                case MY_PERMISSIONS_REQUEST_ALL: {
                    // If request is cancelled, the result arrays are empty.
                    if (grantResults.length > 0
                            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        if(mUploadCallbackAboveL != null){
                            uploadImage(mUploadCallbackAboveL);
                        }
                    } else {
                        Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.no_up_img_permission), Toast.LENGTH_LONG).show();
                    }
                    return true;
                } // Updated case
                case MY_PERMISSIONS_REQUEST_STORAGE: {
                    if (grantResults.length > 0
                            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        if (downUrl != null) {
                            downloadImage(downUrl);
                        }
                    } else {
                        Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.no_down_img_permission), Toast.LENGTH_LONG).show();
                    }
                }
            }
            return false;
        }
    };


    public void downloadImage(String url){
        final AdvancedWebviewModule module = this;
        displayNotification(NOTIFICATION_ID,module.getActivity().getResources().getString(R.string.down_title),module.getActivity().getResources().getString(R.string.down_desc),android.R.drawable.stat_sys_download,null);
        try {
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder().url(url).build();

            client.newCall(request).enqueue(new okhttp3.Callback() {
                public void onFailure(Call call, IOException e) {
                    e.printStackTrace();
                }

                public void onResponse(Call call, Response response) throws IOException {
                    try {
                        if (!response.isSuccessful()) {
                            throw new IOException("Failed to download file: " + response);
                        }
                        Headers responseHeaders = response.headers();
                        String content = responseHeaders.get("Content-Disposition");
                        String contentSplit[] = content.split("filename=");
                        String filename = contentSplit[1].replace("filename=", "").replace("\"", "").trim();

                        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), filename);
                        FileOutputStream fos = new FileOutputStream(file);
                        fos.write(response.body().bytes());
                        fos.close();

                        //Scan media to make the file visible
                        MediaScannerConnection.scanFile(module.getActivity(),
                                new String[]{file.toString()}, null,
                                new MediaScannerConnection.OnScanCompletedListener() {
                                    public void onScanCompleted(String path, Uri uri) {
                                        displayNotification(NOTIFICATION_ID, module.getActivity().getResources().getString(R.string.down_title), module.getActivity().getResources().getString(R.string.down_desc_done), android.R.drawable.stat_sys_download_done, uri);
                                    }
                                });
                    } catch (Exception ex) {
                        //Log.d("Exception", "" + ex);
                        displayNotification(NOTIFICATION_ID, module.getActivity().getResources().getString(R.string.down_title), module.getActivity().getResources().getString(R.string.down_desc_fail), android.R.drawable.stat_sys_download_done, null);
                    }
                }
            });
        }catch(Exception ex){
            displayNotification(NOTIFICATION_ID, module.getActivity().getResources().getString(R.string.down_title), module.getActivity().getResources().getString(R.string.down_desc_fail), android.R.drawable.stat_sys_download_done, null);
        }
    }

    public void displayNotification(int mNotificationId,String title,String description,int icon,Uri imageUri){
        final AdvancedWebviewModule module = this;
        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(module.getActivity(),"default")
                        .setSmallIcon(icon)
                        .setContentTitle(title)
                        .setAutoCancel(true)
                        .setContentText(description);

        if(imageUri != null){
            Intent intent = new Intent(Intent.ACTION_VIEW,imageUri);
            PendingIntent contentIntent =
                    PendingIntent.getActivity(module.getActivity(),
                            0,
                            intent,
                            PendingIntent.FLAG_ONE_SHOT
                    );
            mBuilder.setContentIntent(contentIntent);

        }
// Sets an ID for the notification
// Gets an instance of the NotificationManager service
        NotificationManager mNotifyMgr =
                (NotificationManager) module.getActivity().getSystemService(NOTIFICATION_SERVICE);

// Builds the notification and issues it.
        mNotifyMgr.notify(mNotificationId, mBuilder.build());
    }

    public boolean grantStoragePermission() {
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M){
            return true;
        }
        final AdvancedWebviewModule module = this;

        if(ContextCompat.checkSelfPermission(module.getActivity(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED){
            PermissionAwareActivity activity = getPermissionAwareActivity();

            activity.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_STORAGE,listener);
            /*ActivityCompat.requestPermissions(module.getActivity(),
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    MY_PERMISSIONS_REQUEST_STORAGE);*/
            return false;
        }else {
            return true;
        }
    }


    ...
}

As shown in the downloadImage method in the above code we have used an Android library named Okhttp to download the required file when the url is passed from the download listener attached to the webview. DisplayNotification method is used to display a notification to the user while the file is being downloaded. Also the above code has added the feature to open the image once downloaded by clicking the notification.

To include the Okhttp library to the project just add it as a dependency in android -> app -> build.gradle file as shown below.

dependencies {
    ...
    compile 'com.squareup.okhttp3:okhttp:3.9.1'
    ...
}

In the AdvancedWebViewModule you might have noticed that we have used values from strings.xml file. So next lets add the required key and values to it. You are free to change the values accordingly.

Update android->src->main->res->values->strings.xml file with the following code.

    "down_desc">Downloading...
    "down_desc_done">Download Complete
    "down_desc_fail">Download Failed
    "down_title">App Name
    "no_down_img_permission">Please allow App Name to access storage to download the image
    "no_up_img_permission">Please allow App Name to access storage to upload the image
    "image_chooser">Image Chooser

Finally we will add the enabledDownloadAndroid prop to AdvancedWebview.android.js so that we can enable the file download feature from the react native side.

...
        /**
         * Make upload file available
         */
        uploadEnabledAndroid: PropTypes.bool,
        /**
         * Make download file available
         */
        enabledDownloadAndroid: PropTypes.bool,
...
var webView =
      <NativeWebView
        ref={RCT_WEBVIEW_REF}
        ...
        downloadEnabledAndroid={this.props.downloadEnabledAndroid}
        ...
        />;
... 

After updating above all you could enable the functionality in your application as shown below which concludes part 2 of this series.

.....
import AdvancedWebView from 'path/AdvancedWebview.android';
....

render() {
    const { width, height } = Dimensions.get('window');  
    return (
        <AdvancedWebView
            source={{ uri: "https://lakshinkarunaratne.wordpress.com/"}}
            style={{ flex: 1,width,height}}
            enabledUploadAndroid = {true}
            downloadEnabledAndroid= {true}
        />
        .......
    );
} 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s