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

  1. Download Android Studio.

  2. Install it → during installation, make sure Android SDK and Virtual Device (emulator) are included.

  3. After installation, open Android Studio.

3. Create a New Project

  1. Click New Project.

  2. Select Empty Activity.

  3. 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).

  4. Click Finish.

Select Empty Activity
Fill application Details

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)
}

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top