📱 Device Features · Lesson 39
WebView (Website in App)
When to use a WebView
- Help pages, privacy policy, terms — content you already have on your website.
- A screen that changes often and is easier to update on the server (offers, blog).
- Hybrid apps: native navigation + some web screens (common for school ERPs and portals).
Basic WebView
XML + Kotlin
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
// Activity
binding.webView.settings.javaScriptEnabled = true
binding.webView.webViewClient = WebViewClient() // keep links inside the app
binding.webView.loadUrl("https://codingeasily.in/")
// local HTML from assets/ folder
binding.webView.loadUrl("file:///android_asset/privacy.html")
// HTML string
binding.webView.loadDataWithBaseURL(null, "<h1>Hello</h1><p>From Kotlin</p>", "text/html", "UTF-8", null)WebViewClient and progress
XML – res/layout/fragment_help.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
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" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true" />
</FrameLayout>Back button inside the WebView
Real example: Help screen
Kotlin – ui/help/HelpFragment.kt (complete)
package com.codingeasily.studentdiary.ui.help
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.OnBackPressedCallback
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import com.codingeasily.studentdiary.databinding.FragmentHelpBinding
/** Shows the online Android tutorial inside the app with a WebView. */
class HelpFragment : Fragment() {
private var _binding: FragmentHelpBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentHelpBinding.inflate(inflater, container, false)
return binding.root
}
@SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.webView.settings.javaScriptEnabled = true
binding.webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
_binding?.progress?.isVisible = true
}
override fun onPageFinished(view: WebView?, url: String?) {
_binding?.progress?.isVisible = false
}
}
// After rotation restore the page the user was reading, otherwise open the tutorial
if (savedInstanceState == null || binding.webView.restoreState(savedInstanceState) == null) {
binding.webView.loadUrl(HELP_URL)
}
// Back button goes back inside the WebView first
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (binding.webView.canGoBack()) binding.webView.goBack()
else { isEnabled = false; requireActivity().onBackPressedDispatcher.onBackPressed() }
}
})
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
_binding?.webView?.saveState(outState)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
private const val HELP_URL = "https://codingeasily.in/tutorial/android/"
}
}Open other sites in the browser
Kotlin
binding.webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
val url = request.url
return if (url.host == "codingeasily.in") {
false // our site → load in the WebView
} else {
startActivity(Intent(Intent.ACTION_VIEW, url)) // others (YouTube, WhatsApp, tel:) → system
true
}
}
}JavaScript bridge
Kotlin + HTML
class AppBridge(private val context: Context) {
@JavascriptInterface
fun showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
binding.webView.addJavascriptInterface(AppBridge(this), "Android")
// in your web page (only on your own trusted site):
// <button onclick="Android.showToast('Saved from the web page!')">Save</button>Turning a website into an app
- 1Make the website mobile-friendly first (responsive CSS)
- 2Native shell: toolbar, bottom navigation for the main sections
- 3WebView with WebViewClient, progress bar, back handling, pull-to-refresh
- 4Offline screen: show a native "No internet" view with Retry
- 5Native extras: push notifications, file upload/camera, share
Common mistakes
- No INTERNET permission → "net::ERR_CACHE_MISS".
- Loading
http://pages → blocked by default; use HTTPS. - Rotation reloads the page → save and restore the WebView state (see HelpFragment).
- File inputs (<input type="file">) do nothing → implement
WebChromeClient.onShowFileChooser.
Summary
- WebView + INTERNET +
webViewClient+loadUrl. - Show progress in onPageStarted/onPageFinished; handle Back with
canGoBack(). - Send external links to the system; use a JS bridge only for trusted pages.
✅ Practice
Make a "Privacy Policy" screen that loads an HTML file from assets/, and a "Blog" screen that loads your website with a progress bar and SwipeRefreshLayout.
Frequently Asked Questions
Will Google Play accept an app that is only a WebView?
Apps that just wrap a website with no extra value can be rejected under the minimum functionality policy. Add native features: notifications, offline screens, native navigation, camera upload.
Why does my page open in Chrome instead of the WebView?
Without a WebViewClient, links open in the default browser. Set webView.webViewClient = WebViewClient().
Is enabling JavaScript unsafe?
It is fine for your own trusted pages. Never load untrusted URLs with a JavaScript interface attached, because that page could call your Kotlin code.
