How to Convert a Website into an Android App
Wrap your website in an Android app with WebView. Full code, offline handling, back button, file uploads — and the Play Store rule that gets plain wrappers rejected.
You can put your website inside an Android app in about twenty minutes. Android has a component called WebView that displays web pages, and wrapping your site in one is a genuinely reasonable thing to do.
But read the next section first, because it decides whether your app ever reaches the Play Store.
The rule nobody mentions
Google Play policy rejects apps whose only function is to display a website. The wording is about apps that provide "no independent value" — a browser with your logo on it.
This gets apps rejected every day. If you plan to publish, the app must do at least one thing the website cannot:
- Push notifications — the strongest and most common answer
- Offline access to some content
- Camera or file uploads handled natively
- A home screen widget
- Biometric login
- Native navigation — a real bottom bar rather than the website menu
If the app is only for internal use, a company intranet, or side-loading to your own staff, none of this applies. Build the wrapper and move on.
When a WebView app is the right choice
| Good fit | Poor fit |
|---|---|
| Your site is already mobile-friendly | The site is not responsive |
| Content changes often and you do not want app updates | You need smooth animation or games |
| Internal tools and dashboards | Heavy camera or sensor use |
| A small budget and one codebase | You want the app to feel truly native |
Be honest about the first row. If your website looks bad on a phone, it will look exactly as bad inside the app. Fix the website first — that work benefits everyone, not just app users.
Step 1: Create the project
File → New → New Project → Empty Views Activity. Package name com.yourname.yourapp, minimum SDK API 24.
Step 2: Permissions
In AndroidManifest.xml, above <application>:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Without the internet permission the WebView shows a blank white screen and no error, which wastes a surprising amount of debugging time.
Step 3: The layout
res/layout/activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
Wrapping it in SwipeRefreshLayout gives you pull-to-refresh, which people expect. Add the dependency in build.gradle:
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
Step 4: The code
package com.yourname.yourapp;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.activity.OnBackPressedCallback;
import androidx.appcompat.app.AppCompatActivity;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
public class MainActivity extends AppCompatActivity {
private WebView webView;
private SwipeRefreshLayout swipeRefresh;
private static final String HOME_URL = "https://yourwebsite.com";
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView = findViewById(R.id.webView);
swipeRefresh = findViewById(R.id.swipeRefresh);
WebSettings s = webView.getSettings();
s.setJavaScriptEnabled(true); // nearly every site needs this
s.setDomStorageEnabled(true); // localStorage — logins break without it
s.setLoadWithOverviewMode(true);
s.setUseWideViewPort(true);
s.setSupportZoom(false); // a proper app does not pinch-zoom
s.setCacheMode(WebSettings.LOAD_DEFAULT);
webView.setWebViewClient(new WebViewClient() {
// Keep links inside the app, but send other domains to the browser
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
if (url.contains("yourwebsite.com")) {
return false; // load it here
}
startActivity(new android.content.Intent(
android.content.Intent.ACTION_VIEW, request.getUrl()));
return true; // hand it to the browser
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
swipeRefresh.setRefreshing(true);
}
@Override
public void onPageFinished(WebView view, String url) {
swipeRefresh.setRefreshing(false);
}
@Override
public void onReceivedError(WebView view, WebResourceRequest req, WebResourceError err) {
if (req.isForMainFrame()) {
view.loadUrl("file:///android_asset/offline.html");
}
}
});
swipeRefresh.setOnRefreshListener(() -> webView.reload());
// Back button should walk back through pages, not close the app
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (webView.canGoBack()) {
webView.goBack();
} else {
setEnabled(false);
getOnBackPressedDispatcher().onBackPressed();
}
}
});
webView.loadUrl(HOME_URL);
}
}
Step 5: The offline page
Without this, losing signal shows the ugly grey "Webpage not available" screen. Create app/src/main/assets/offline.html:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: system-ui, sans-serif; display: flex; flex-direction: column;
align-items: center; justify-content: center; height: 100vh; margin: 0;
background: #f8fafc; color: #334155; text-align: center; padding: 24px; }
h1 { font-size: 1.4rem; margin-bottom: 8px; color: #0f172a; }
button { margin-top: 20px; padding: 12px 26px; border: none; border-radius: 999px;
background: #0d9488; color: #fff; font-size: 1rem; }
</style>
</head>
<body>
<h1>No internet connection</h1>
<p>Check your connection and try again.</p>
<button onclick="location.reload()">Retry</button>
</body>
</html>
Step 6: File uploads
If your site has any file or photo upload, it silently does nothing in a WebView until you add this. Users tap the button, no picker opens, and they assume the app is broken.
import android.content.Intent;
import android.net.Uri;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
private ValueCallback<Uri[]> filePathCallback;
private final ActivityResultLauncher<Intent> filePicker =
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (filePathCallback == null) return;
Uri[] results = null;
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
Uri uri = result.getData().getData();
if (uri != null) { results = new Uri[]{uri}; }
}
filePathCallback.onReceiveValue(results);
filePathCallback = null;
});
// add inside onCreate, after the WebViewClient:
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> callback,
FileChooserParams params) {
filePathCallback = callback;
filePicker.launch(params.createIntent());
return true;
}
});
Step 7: Make it feel like an app
Four small things that change the impression completely:
- A splash screen, so launch does not begin with a white flash
- A proper app icon — File → New → Image Asset
- Theme colours that match your site, so the status bar is not a mismatched grey
- Hide the website header inside the app. Inject a small CSS rule in
onPageFinishedto remove the site navigation, since the app has its own.
Common problems
A blank white screen
Missing internet permission, or an http:// URL. Since Android 9 plain HTTP is blocked by default — use HTTPS. See how to install a free SSL certificate.
The site loads but looks broken
JavaScript is off. Set setJavaScriptEnabled(true), and setDomStorageEnabled(true) as well — many sites store their session in localStorage and log the user straight back out without it.
Links open in Chrome instead of the app
You are missing shouldOverrideUrlLoading, or your domain check does not match. Watch for www. versus the bare domain.
The back button closes the app
Add the OnBackPressedCallback above. The old onBackPressed() override is deprecated.
File upload does nothing
onShowFileChooser is missing. Nothing appears and nothing errors, which is why this one is so confusing.
Play Store rejected the app
Policy 4.4 — no independent value. Add push notifications or offline content and resubmit with a note explaining what the app does beyond the website.
Is there a better option?
Two worth knowing about:
A Progressive Web App (PWA) gives users an installable icon with offline support and push notifications, with no app store at all. Cheaper and faster if store presence is not the goal.
Trusted Web Activity (TWA) is Google's own way of shipping a website as an app. It uses Chrome under the hood, so the site looks identical and there is no address bar. It also passes review more easily than a WebView wrapper, because Google designed it for this.
If you want an app icon on the Play Store for an existing site, TWA is usually the better answer than WebView. Use WebView when you need to mix web content with native features.
Where to go next
- Add AdMob ads to your app
- Make a flashlight app — a simpler first project
- Speed up the website itself — the app is only as fast as the site