Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

Build a Simple Android Web App in WebView with Support of Download/Upload Feature

5.00/5 (1 vote)
19 Mar 2023CPOL4 min read 14.9K   357  
Convert Website into Android Web App with WebView
A guide on building Android Web App in WebView with support of Download/Upload feature

Image 1

References:

This article demonstrates one of the ways to build an Android Web App that support uploading and downloading files.

To begin, first download Android Studio from the official site:

At the time of writing this, I’m using the latest version which is: Android Studio Electri Eel | 2022.1.1 Patch

Image 2

At Android Studio, create a new Phone project, select Empty Activity template.

Image 3

Fill in basic project info:

Image 4

Read about [Package Name].

Depends on your application needs, if you wish your app to be able to run on most Android devices, select API 19: Android 4.4 (KitKat) (info is based at the time of writing this).

Language: Java (This article will use Java.)

Click Finish to start creating the app.

Wait for a moment for Android Studio to load and creates project files. When ready, you should be able to see something like this:

Image 5

Setup App Icons

Before continuing, we may first setup the App Icons (*You can do this later).

Close Android Studio, and go to this website (App Icon Generators):

Upload your favorite icon to the website and it will generate various sizes of App Icons for building your Android project.

*Acknowledgement: Thanks to [icons8.com] for sponsoring the icon used in this article.

Image 6

Download the generated icons:

Image 7

Extract the zip content and copy from the Android’s icon set folder:

zip extracted content folder...\android\

To the project icon resource folder:

project_folder...\<project name>\app\src\main\res\

Image 8

Reopen Android Studio and the project.

Now, the project has two sets of icons:

  • Set 1: Downloaded from easyappicon.com
  • Set 2: Original default icons added by Android Studio

This resulting duplicates icon existed in the project which will cause build error. Therefore, we need to delete the default icons added by Android Studio.

Go to the folder:

app > res > mipmap > ic_launcher

and delete all *.webp files.

Image 9

and go to another folder:

app > res > mipmap > ic_launcher_round

and delete all *.webp files:

Image 10

At the folder [ app > res > drawable ], delete the following files:

  • ic_launcher_background.xml
  • ic_launcher_foreground.xml

And replace with your own edited PNG images:

  • ic_launcher_background.png
  • ic_launcher_foreground.png

For more information on changing the App Icons, please refer to the following Android Developer Documentation:

Setup the Layout

Next, edit the ActionBar. Open the following theme files:

app > res > values > themes.xml
app > res > values > themes.xml (night)

Image 11

Change both files of this line: (from DarkActionBar):

XML
<style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">

To (NoActionBar):

XML
<style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">

After edit, the theme file’s content will look something like this:

XML
<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_200</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/black</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_200</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="21">
         ?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>
</resources>

Next, edit the layout file at:

app > res > layout > activity_main.xml

Click [Code] to view the XML designer code.

Image 12

Here’s the initial code:

Image 13

Delete the TextView, and add a WebView.

XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <WebView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/webView">
    </WebView>

</androidx.constraintlayout.widget.ConstraintLayout>

Change the Layout from:

androidx.constraintlayout.widget.ConstraintLayout

to:

RelativeLayout

After edit, the code will look something like this:

XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <WebView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/webView">
    </WebView>

</RelativeLayout>

Setup App Permissions

The next step is to enable the permission for the app to access internet and download/upload files.

Open the Android manifest file (AndroidManifest.xml) at:

app > manifest > AndroidManifest.xml

Insert three uses-permission request lines:

  • android.permission.INTERNET: Permission to access internet
  • android.permission.READ_EXTERNAL_STORAGE: able to select files from Android storage (for uploading files)
  • android.permission.WRITE_EXTERNAL_STORAGE: for saving downloaded files
XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE">
    </uses-permission>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
    </uses-permission>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApp"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Adding Asset Directory and Files

Right click the folder [app] > New > Directory

Select src\main\assets.

Image 14

Right click the newly created assets folder > New > File

Name the file as no_internet.html.

Double click the file and enter some html, for example:

HTML
<html>
<head></head>
<body>
<h1>No Internet</h1>
Please check the internet connection.
</body>
</html>

Image 15

Coding the WebView

Read more: Android Developer Documentation on WebView

Now, we come to the WebView coding part. Open the MainActivity.java file at:

app > java > ..package name... > MainActivity

// example:

app > java > com.company.product > MainActivity

Here’s the initial code:

Image 16

Import the following class libraries:

Java
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.webkit.DownloadListener;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

Declare the objects of WebView, ProgressDialog and a few global variables.

Java
public class MainActivity extends AppCompatActivity {

    // if your website starts with www, exclude it
    private static final String myWebSite = "example.com";

    WebView webView;
    ProgressDialog progressDialog;

    // for handling file upload, set a static value, any number you like
    // this value will be used by WebChromeClient during file upload
    private static final int file_chooser_activity_code = 1;
    private static ValueCallback<Uri[]> mUploadMessageArr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // initialize the progressDialog
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setCancelable(true);
        progressDialog.setMessage("Loading...");
        progressDialog.show();
    }
}

Handling Web Page Browsing Activities

In the class of MainActivity, create a WebViewClient to handle web browsing activities:

Java
class myWebViewClient extends android.webkit.WebViewClient {

    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
        //showing the progress bar once the page has started loading
        progressDialog.show();
    }

    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        // hide the progress bar once the page has loaded
        progressDialog.dismiss();
    }

    @Override
    public void onReceivedError(WebView view,
           WebResourceRequest request, WebResourceError error) {
        super.onReceivedError(view, request, error);
        // show the error message = no internet access
        webView.loadUrl("file:///android_asset/no_internet.html");
        // hide the progress bar on error in loading
        progressDialog.dismiss();
        Toast.makeText(getApplicationContext(),"Internet issue",
                       Toast.LENGTH_SHORT).show();
    }
}

Handling File Upload Activities

File upload will be triggered from the html input type of file:

HTML
<html>
<head></head>
<body>
    <form>
        <input type="file" />
    </form>
</body>
</html>

Next, in the class of MainActivity, create a WebChomeClient object to handle file uploading task.

Java
// Calling WebChromeClient to select files from the device
public class myWebChromeClient extends WebChromeClient {
    @SuppressLint("NewApi")
    @Override
    public boolean onShowFileChooser(WebView webView,
    ValueCallback<Uri[]> valueCallback, FileChooserParams fileChooserParams) {

        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);

        // set single file type, e.g. "image/*" for images
        intent.setType("*/*");

        // set multiple file types
        String[] mimeTypes = {"image/*", "application/pdf"};
        intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);

        Intent chooserIntent = Intent.createChooser(intent, "Choose file");
        ((Activity) webView.getContext()).startActivityForResult
                    (chooserIntent, file_chooser_activity_code);

        // Save the callback for handling the selected file
        mUploadMessageArr = valueCallback;

        return true;
    }
}

// after the file chosen handled, variables are returned back to MainActivity
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    // check if the chrome activity is a file choosing session
    if (requestCode == file_chooser_activity_code) {
        if (resultCode == Activity.RESULT_OK && data != null) {
            Uri[] results = null;

            // Check if response is a multiple choice selection containing the results
            if (data.getClipData() != null) {
                int count = data.getClipData().getItemCount();
                results = new Uri[count];
                for (int i = 0; i < count; i++) {
                    results[i] = data.getClipData().getItemAt(i).getUri();
                }
            } else if (data.getData() != null) {
                // Response is a single choice selection
                results = new Uri[]{data.getData()};
            }

            mUploadMessageArr.onReceiveValue(results);
            mUploadMessageArr = null;
        } else {
            mUploadMessageArr.onReceiveValue(null);
            mUploadMessageArr = null;
            Toast.makeText(MainActivity.this, "Error getting file",
                           Toast.LENGTH_LONG).show();
        }
    }
}

Handling File Download Activities

In the class of MainActivity, create a download event listener:

Java
DownloadListener downloadListener = new DownloadListener() {
    @Override
    public void onDownloadStart(String url, String userAgent,
    String contentDisposition, String mimetype, long contentLength) {

        progressDialog.dismiss();
        Intent i = new Intent(Intent.ACTION_VIEW);

        // example of URL = https://www.example.com/invoice.pdf
        i.setData(Uri.parse(url));
        startActivity(i);
    }
};

This listener might not able to handle downloads that need login session.

Initializing WebView

Back to the method of onCreate( ), continue the initialization of WebView:

Java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // initialize the progressDialog
    progressDialog = new ProgressDialog(MainActivity.this);
    progressDialog.setCancelable(true);
    progressDialog.setMessage("Loading...");
    progressDialog.show();

    // get the webview from the layout
    webView = findViewById(R.id.webView);

    // for handling Android Device [Back] key press
    webView.canGoBackOrForward(99);

    // handling web page browsing mechanism
    webView.setWebViewClient(new myWebViewClient());

    // handling file upload mechanism
    webView.setWebChromeClient(new myWebChromeClient());

    // some other settings
    WebSettings settings = webView.getSettings();
    settings.setJavaScriptEnabled(true);
    settings.setAllowFileAccess(true);
    settings.setAllowFileAccessFromFileURLs(true);
    settings.setUserAgentString(new WebView(this).getSettings().getUserAgentString());

    // set the downlaod listner
    webView.setDownloadListener(downloadListener);

    // load the website
    webView.loadUrl("https://" + myWebSite);
}

Handling [Back] Key Pressed on Android Device:

In the class of MainActivity, add a function to handle [BackPressed]:

Java
@Override
public void onBackPressed() {
    if(webView.canGoBack()){
        webView.goBack();
    } else {
        finish();
    }
}

The full page of code will look something like this: [https://github.com/adriancs2/android.webview.upload.download.......MainActivity.java]

Finally, let's test out the app.

Image 17

To connect a real Android Phone to Android Studio, you may enable "Developer Mode" then turn on "USB Debugging" on the Android device. Connect the Android to PC through USB cable, Android Studio should be able to catch it up and display it at the device list.

To distribute it, you may build it as APK. At menu, go to [Build] > [Generate Signed Bundle / APK...] > select [APK] and follow on screen instructions.

To build for uploading to Google PlayStore, you may select [Android App Bundle] and then follow on screen instructions.

The full source code is available at the top of this article.

Thank you and happy coding!

*Acknowledge: Feature/Thumbnail Image is provided by Denny Müller at Unsplash.com.

Alternative

History

  • 19th March, 2023 - Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)