A guide on building Android Web App in WebView with support of Download/Upload feature
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
At Android Studio, create a new Phone project, select Empty Activity template.
Fill in basic project info:
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:
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.
Download the generated icons:
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\
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.
and go to another folder:
app > res > mipmap > ic_launcher_round
and delete all *.webp files:
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)
Change both files of this line: (from DarkActionBar
):
<style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
To (NoActionBar
):
<style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
After edit, the theme file’s content will look something like this:
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<item name="android:statusBarColor" tools:targetApi="21">
?attr/colorPrimaryVariant</item>
</style>
</resources>
Next, edit the layout file at:
app > res > layout > activity_main.xml
Click [Code] to view the XML designer code.
Here’s the initial code:
Delete the TextView
, and add a WebView
.
="1.0"="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:
="1.0"="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
="1.0"="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.
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>
<head></head>
<body>
<h1>No Internet</h1>
Please check the internet connection.
</body>
</html>
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:
Import the following class libraries:
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.
public class MainActivity extends AppCompatActivity {
private static final String myWebSite = "example.com";
WebView webView;
ProgressDialog progressDialog;
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);
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:
class myWebViewClient extends android.webkit.WebViewClient {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
progressDialog.show();
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
progressDialog.dismiss();
}
@Override
public void onReceivedError(WebView view,
WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
webView.loadUrl("file:///android_asset/no_internet.html");
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>
<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.
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);
intent.setType("*/*");
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);
mUploadMessageArr = valueCallback;
return true;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == file_chooser_activity_code) {
if (resultCode == Activity.RESULT_OK && data != null) {
Uri[] results = null;
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) {
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:
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);
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
:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setCancelable(true);
progressDialog.setMessage("Loading...");
progressDialog.show();
webView = findViewById(R.id.webView);
webView.canGoBackOrForward(99);
webView.setWebViewClient(new myWebViewClient());
webView.setWebChromeClient(new myWebChromeClient());
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setAllowFileAccess(true);
settings.setAllowFileAccessFromFileURLs(true);
settings.setUserAgentString(new WebView(this).getSettings().getUserAgentString());
webView.setDownloadListener(downloadListener);
webView.loadUrl("https://" + myWebSite);
}
Handling [Back] Key Pressed on Android Device:
In the class of MainActivity
, add a function to handle [BackPressed
]:
@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.
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