PHP WEBSITE TO ANDROID APP

IDE
Integrated Development Environment

Programming Language
Kotlin is a modern programming language used mainly for making Android apps.
Step-by-Step: WebView Wrapper for Android (in Kotlin)
Note : Replace website’s url and package name with your website’s url and package name
1. What is WebView?
WebView is a special Android component that allows you to open a website inside a mobile app.
It works like a mini-browser inside your app.
Example: Instead of users opening Chrome and typing your site, you can make an app that shows your site directly.
So, your app will be basically a container for https://rsportfolio.com/
.
2. Install Android Studio
Download Android Studio.
Install it → during installation, make sure Android SDK and Virtual Device (emulator) are included.
After installation, open Android Studio.
3. Create a New Project
Click New Project.
Select Empty Activity.
Fill details:
App Name: RSPortfolio
Package Name:
com.rsportfolio.app
(unique ID for your app).Language: Kotlin.
Minimum SDK: API 21 (Android 5.0, works on 99% of devices).
Click Finish.
4. Add Internet Permission (For Genrate Icons visit https://www.appicon.co/ )
Your app must have internet access to load your site.
👉 Open app/src/main/AndroidManifest.xml
and add inside <manifest>
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.rsportfolio.app">
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Optional: For writing files on Android 10 and below -->
<!-- For Android 11+, consider using scoped storage instead of WRITE_EXTERNAL_STORAGE -->
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> -->
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.RSPortfolio">
<activity
android:name=".MainActivity"
android:exported="true"
tools:ignore="MissingClass">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
5. Add WebView Wrapper
👉 Open app/src/main/java/packagename/MainActivity
package com.rsportfolio.app
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.os.Bundle
import android.os.Environment
import android.view.View
import android.webkit.*
import android.widget.FrameLayout
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.core.animation.doOnEnd
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@Suppress("DEPRECATION")
class MainActivity : ComponentActivity() {
companion object {
var preloadedWebView: WebView? = null
}
private lateinit var webView: WebView
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
// White splash screen
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { true }
super.onCreate(savedInstanceState)
// Hardware acceleration
window.setFlags(
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)
// Ensure bottom buttons are visible
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
// Handle back press
val backCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (::webView.isInitialized && webView.canGoBack()) {
webView.goBack()
} else {
isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
}
}
onBackPressedDispatcher.addCallback(backCallback)
// Use preloaded WebView or initialize
webView = preloadedWebView ?: WebView(this).apply {
preloadWebView(this)
}
// Wrap WebView in a FrameLayout
val container = FrameLayout(this)
container.fitsSystemWindows = true
container.addView(webView)
setContentView(container)
// Fade-in animation after page is ready
webView.alpha = 0f
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
view?.settings?.blockNetworkImage = false
ObjectAnimator.ofFloat(webView, "alpha", 0f, 1f).apply {
duration = 400
doOnEnd { splashScreen.setKeepOnScreenCondition { false } }
start()
}
}
}
if (webView.url == null) {
webView.loadUrl("https://rsportfolio.com/")
}
}
@SuppressLint("SetJavaScriptEnabled")
private fun preloadWebView(view: WebView) {
preloadedWebView = view
view.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
loadWithOverviewMode = true
useWideViewPort = true
setSupportZoom(true)
builtInZoomControls = true
displayZoomControls = false
cacheMode = WebSettings.LOAD_DEFAULT
loadsImagesAutomatically = true
blockNetworkImage = true
}
view.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
view?.loadUrl(request?.url.toString())
return true
}
override fun onPageFinished(view: WebView?, url: String?) {
view?.settings?.blockNetworkImage = false
}
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
val url = request?.url.toString()
if (url.contains("ads") || url.contains("doubleclick")) {
return WebResourceResponse("text/plain", "utf-8", null)
}
return super.shouldInterceptRequest(view, request)
}
}
// Download listener for both CSV and PDF
view.setDownloadListener { url, _, _, _, _ ->
when {
url.endsWith(".pdf") -> handleDownload(url, "pdf")
url.endsWith(".csv") -> handleDownload(url, "csv")
else -> handleDownload(url)
}
}
view.loadUrl("https://rsportfolio.com/")
}
private fun handleDownload(url: String, fileType: String = "csv") {
// Determine MIME type and file extension
val (mimeType, extension) = when(fileType.lowercase()) {
"pdf" -> "application/pdf" to "pdf"
"csv" -> "text/csv" to "csv"
else -> "application/octet-stream" to "dat"
}
// Generate dynamic filename
val fileName = when(fileType.lowercase()) {
"pdf" -> "schemes.$extension"
"csv" -> "Stock_Details_" + java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date()) + ".$extension"
else -> "File_" + java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date()) + ".$extension"
}
val request = DownloadManager.Request(url.toUri()).apply {
addRequestHeader("cookie", CookieManager.getInstance().getCookie(url))
setMimeType(mimeType)
setTitle(fileName)
setDescription("Downloading $fileName")
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
allowScanningByMediaScanner()
setAllowedOverMetered(true)
setAllowedOverRoaming(true)
}
val dm = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
dm.enqueue(request)
Toast.makeText(this, "$fileName download started", Toast.LENGTH_SHORT).show()
}
}
6. build.gradle.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "com.rsportfolio.app"
compileSdk = 36
defaultConfig {
applicationId = "com.rsportfolio.app"
minSdk = 21
targetSdk = 35
versionCode = 4
versionName = "4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.androidx.core.ktx.v1120)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.core.splashscreen)
}