android kotlin - WebView file download example






MainActivity.kt



package com.cfsuman.kotlinexamples

import android.Manifest
import android.content.Context
import android.graphics.Bitmap
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
import android.os.Build
import android.view.View
import android.app.DownloadManager
import android.net.Uri
import android.os.Environment
import android.webkit.*


class MainActivity : AppCompatActivity() {
private val url = "https://en.savefrom.net/"
private val PermissionsRequestCode = 123
private lateinit var managePermissions: ManagePermissions

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// Initialize a list of required permissions to request runtime
val list = listOf<String>(
Manifest.permission.WRITE_EXTERNAL_STORAGE
)

// Initialize a new instance of ManagePermissions class
managePermissions = ManagePermissions(this,list,PermissionsRequestCode)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) managePermissions.checkPermissions()

// Get the web view settings instance
val settings = web_view.settings

// Enable java script in web view
settings.javaScriptEnabled = true

// Enable and setup web view cache
settings.setAppCacheEnabled(true)
settings.cacheMode = WebSettings.LOAD_DEFAULT
settings.setAppCachePath(cacheDir.path)


// Enable zooming in web view
settings.setSupportZoom(true)
settings.builtInZoomControls = true
settings.displayZoomControls = true


// Zoom web view text
settings.textZoom = 125


// Enable disable images in web view
settings.blockNetworkImage = false
// Whether the WebView should load image resources
settings.loadsImagesAutomatically = true


// More web view settings
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
settings.safeBrowsingEnabled = true // api 26
}
//settings.pluginState = WebSettings.PluginState.ON
settings.useWideViewPort = true
settings.loadWithOverviewMode = true
settings.javaScriptCanOpenWindowsAutomatically = true
settings.mediaPlaybackRequiresUserGesture = false


// More optional settings, you can enable it by yourself
settings.domStorageEnabled = true
settings.setSupportMultipleWindows(true)
settings.loadWithOverviewMode = true
settings.allowContentAccess = true
settings.setGeolocationEnabled(true)
settings.allowUniversalAccessFromFileURLs = true
settings.allowFileAccess = true

// WebView settings
web_view.fitsSystemWindows = true


/*
if SDK version is greater of 19 then activate hardware acceleration
otherwise activate software acceleration
*/
web_view.setLayerType(View.LAYER_TYPE_HARDWARE, null)


// Set web view client
web_view.webViewClient = object: WebViewClient(){
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
// Page loading started
// Do something
toast("Page loading.")
}

override fun onPageFinished(view: WebView, url: String) {
// Page loading finished
// Display the loaded page title in a toast message
toast("Page loaded: ${view.title}")
}
}


// Set web view chrome client
web_view.webChromeClient = object: WebChromeClient(){
override fun onProgressChanged(view: WebView, newProgress: Int) {
progress_bar.progress = newProgress
}
}


// Set web view download listener

web_view.setDownloadListener(DownloadListener {
url,
userAgent,
contentDescription,
mimetype,
contentLength ->

// Initialize download request
val request = DownloadManager.Request(Uri.parse(url))

// Get the cookie
val cookies = CookieManager.getInstance().getCookie(url)

// Add the download request header
request.addRequestHeader("Cookie",cookies)
request.addRequestHeader("User-Agent",userAgent)

// Set download request description
request.setDescription("Downloading requested file....")

// Set download request mime tytpe
request.setMimeType(mimetype)

// Allow scanning
request.allowScanningByMediaScanner()

// Download request notification setting
request.setNotificationVisibility(
DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)

// Guess the file name
val fileName = URLUtil.guessFileName(url, contentDescription, mimetype)

// Set a destination storage for downloaded file
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)

// Set request title
request.setTitle(URLUtil.guessFileName(url, contentDescription, mimetype));


// DownloadManager request more settings
request.setAllowedOverMetered(true)
request.setAllowedOverRoaming(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
request.setRequiresCharging(false)
request.setRequiresDeviceIdle(false)
}
request.setVisibleInDownloadsUi(true)


// Get the system download service
val dManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager

// Finally, request the download to system download service
dManager.enqueue(request)
})


// Load button click listener
button_load.setOnClickListener{
// Load url in a web view
web_view.loadUrl(url)
}

}



// Receive the permissions request result
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
grantResults: IntArray) {
when (requestCode) {
PermissionsRequestCode ->{
val isPermissionsGranted = managePermissions
.processPermissionsResult(requestCode,permissions,grantResults)

if(isPermissionsGranted){
// Do the task now
toast("Permission(s) granted.")
}else{
toast("Permission(s) denied.")
}
return
}
}
}



// Handle back button press in web view
override fun onBackPressed() {
if (web_view.canGoBack()) {
// If web view have back history, then go to the web view back history
web_view.goBack()
toast("Going to back history")
} else {
super.onBackPressed()
}
}
}


// Extension function to show toast message
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}





activity_main.xml



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#edfaed"
android:padding="0dp"
android:orientation="vertical"
>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
/>
<Button
android:id="@+id/button_load"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Load URL"
android:textAllCaps="false"
/>
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
</LinearLayout>




ManagePermissions.kt



package com.cfsuman.kotlinexamples

import android.app.Activity
import android.content.pm.PackageManager
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog


class ManagePermissions(private val activity: Activity,
private val list: List<String>,val code:Int
) {

// Check permissions at runtime
fun checkPermissions() {
if (isPermissionsGranted() != PackageManager.PERMISSION_GRANTED) {
showAlert()
} else {
activity.toast("Permission(s) already granted.")
}
}


// Check permissions status
private fun isPermissionsGranted(): Int {
// PERMISSION_GRANTED : Constant Value: 0
// PERMISSION_DENIED : Constant Value: -1
var counter = 0;
for (permission in list) {
counter += ContextCompat.checkSelfPermission(activity, permission)
}
return counter
}


// Find the first denied permission
private fun deniedPermission(): String {
for (permission in list) {
if (ContextCompat.checkSelfPermission(activity, permission)
== PackageManager.PERMISSION_DENIED) return permission
}
return ""
}


// Show alert dialog to request permissions
private fun showAlert() {
val builder = AlertDialog.Builder(activity)
builder.setTitle("Need permission(s)")
builder.setMessage("Permission(s) required to do the task.")
builder.setPositiveButton("OK", { _, _ -> requestPermissions() })
builder.setNeutralButton("Cancel", null)
val dialog = builder.create()
dialog.show()
}


// Request the permissions at run time
private fun requestPermissions() {
val permission = deniedPermission()
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
// Show an explanation asynchronously
activity.toast("Should show an explanation.")
} else {
ActivityCompat.requestPermissions(activity, list.toTypedArray(), code)
}
}


// Process permissions result
fun processPermissionsResult(requestCode: Int, permissions: Array<String>,
grantResults: IntArray): Boolean {
var result = 0
if (grantResults.isNotEmpty()) {
for (item in grantResults) {
result += item
}
}
if (result == PackageManager.PERMISSION_GRANTED) return true
return false
}
}





AndroidManifest.xml [Permissions]



<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>










android kotlin - WebView example

MainActivity.kt

package com.cfsuman.kotlintutorials

import android.app.Activity
import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import android.os.Bundle
import android.view.View
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Button
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog


class MainActivity : Activity() {
    lateinit var webView:WebView
    private val url = "https://www.android--code.com"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        // Get the widgets references from XML layout
        webView = findViewById(R.id.webView)
        val buttonLoad: Button = findViewById(R.id.buttonLoad)
        val buttonBack: Button = findViewById(R.id.buttonBack)
        val buttonForward: Button = findViewById(R.id.buttonForward)
        val progressBar: ProgressBar = findViewById(R.id.progressBar)


        // Get the web view settings instance
        val settings = webView.settings

        // Enable java script in web view
        settings.javaScriptEnabled = true


        // Enable and setup web view cache
        //settings.setAppCacheEnabled(true)
        settings.cacheMode = WebSettings.LOAD_DEFAULT
        //settings.setAppCachePath(cacheDir.path)


        // Enable zooming in web view
        settings.setSupportZoom(true)
        settings.builtInZoomControls = true
        settings.displayZoomControls = true


        // Zoom web view text
        settings.textZoom = 110


        // Enable disable images in web view
        settings.blockNetworkImage = false
        // Whether the WebView should load image resources
        settings.loadsImagesAutomatically = true


        // More web view settings
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            settings.safeBrowsingEnabled = true  // api 26
        }
        //settings.pluginState = WebSettings.PluginState.ON
        settings.useWideViewPort = true
        settings.loadWithOverviewMode = true
        settings.javaScriptCanOpenWindowsAutomatically = true
        settings.mediaPlaybackRequiresUserGesture = false


        // More optional settings, you can enable it by yourself
        settings.domStorageEnabled = true
        settings.setSupportMultipleWindows(true)
        settings.loadWithOverviewMode = true
        settings.allowContentAccess = true
        settings.setGeolocationEnabled(true)
        settings.allowUniversalAccessFromFileURLs = true
        settings.allowFileAccess = true

        // WebView settings
        webView.fitsSystemWindows = true


        // Activate hardware acceleration
        webView.setLayerType(View.LAYER_TYPE_HARDWARE, null)


        // Set web view client
        webView.webViewClient = object: WebViewClient(){
            override fun onPageStarted(view: WebView, url: String,
                                       favicon: Bitmap?) {
                // Page loading started
                // Do something
                toast("Page loading.")

                // Enable disable back forward button
                buttonBack.isEnabled = webView.canGoBack()
                buttonForward.isEnabled = webView.canGoForward()
            }

            override fun onPageFinished(view: WebView, url: String) {
                // Page loading finished
                // Display the loaded page title in a toast message
                toast("Page loaded: ${view.title}")

                // Enable disable back forward button
                buttonBack.isEnabled = webView.canGoBack()
                buttonForward.isEnabled = webView.canGoForward()
            }
        }


        // Set web view chrome client
        webView.webChromeClient = object: WebChromeClient(){
            override fun onProgressChanged(
                view: WebView, newProgress: Int) {
                progressBar.progress = newProgress
            }
        }


        // Load button click listener
        buttonLoad.setOnClickListener{
            // Load url in a web view
            webView.loadUrl(url)
        }


        // Back button click listener
        buttonBack.setOnClickListener{
            if(webView.canGoBack()){
                // Go to back history
                webView.goBack()
            }
        }


        // Forward button click listener
        buttonForward.setOnClickListener{
            if(webView.canGoForward()){
                // Go to forward history
                webView.goForward()
            }
        }
    }



    // Method to show app exit dialog
    private fun showAppExitDialog() {
        val builder = AlertDialog.Builder(this)

        builder.setTitle("Please confirm")
        builder.setMessage("No back history found," +
                " want to exit the app?")
        builder.setCancelable(true)

        builder.setPositiveButton("Yes") { _, _ ->
            // Do something when user want to exit the app
            // Let allow the system to handle
            // the event, such as exit the app
            super@MainActivity.onBackPressed()
        }

        builder.setNegativeButton("No") { _, _ ->
            // Do something when want to stay in the app
            toast("thank you.")
        }

        // Create the alert dialog using alert dialog builder
        val dialog = builder.create()

        // Finally, display the dialog when user press back button
        dialog.show()
    }



    // Handle back button press in web view
    override fun onBackPressed() {
        if (webView.canGoBack()) {
            // If web view have back history,
            // then go to the web view back history
            webView.goBack()
            toast("Going to back history")
        } else {
            // Ask the user to exit the app or stay in here
            showAppExitDialog()
        }
    }
}



// Extension function to show toast message
fun Context.toast(message: String) {
    Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/rootLayout"
    android:background="#DCDCDC">

    <ProgressBar
        android:id="@+id/progressBar"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/buttonLoad"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:text="Load URL"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/progressBar" />

    <Button
        android:id="@+id/buttonBack"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:text="Back"
        app:layout_constraintBottom_toBottomOf="@+id/buttonLoad"
        app:layout_constraintStart_toEndOf="@+id/buttonLoad" />

    <Button
        android:id="@+id/buttonForward"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:text="Forward"
        app:layout_constraintBottom_toBottomOf="@+id/buttonBack"
        app:layout_constraintStart_toEndOf="@+id/buttonBack" />

    <WebView
        android:id="@+id/webView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/buttonLoad" />

</androidx.constraintlayout.widget.ConstraintLayout>

Android kotlin: How to request permissions at runtime

Introduction

This code demonstrates how to request permissions at runtime in an Android application written in Kotlin. The Android operating system introduced runtime permissions in Android Marshmallow (API level 23). This means apps no longer get granted all permissions they declare in the manifest file at install time. Instead, the app must explicitly request permissions from the user when it needs to access sensitive features or data on the device.

Breakdown of the Code

The code consists of three main parts:

  1. MainActivity.kt: This file contains the main activity class for the application. It defines a button that the user can click to initiate the permission request process. It also handles the result of the permission request.

  2. ManagePermissions.kt: This file defines a separate class called ManagePermissions that encapsulates the logic for checking permissions, requesting permissions, and processing the permission request result. This class promotes better code organization and reusability.

  3. Layout and Manifest Files (activity_main.xml and AndroidManifest.xml): These files define the user interface layout for the activity and declare the permissions the app needs in its manifest file.

Here's a more detailed breakdown of the functionality within these files:

  • MainActivity.kt:

    • It defines a list of permissions the app requires.
    • It initializes an instance of the ManagePermissions class to handle permission related tasks.
    • Clicking the button calls the checkPermissions function from the ManagePermissions class to initiate the permission request process.
    • The onRequestPermissionsResult method receives the result of the permission request and displays a toast message based on whether the permissions were granted or denied.
  • ManagePermissions.kt:

    • The checkPermissions function first checks if all required permissions are already granted. If not, it shows an alert dialog explaining why the permissions are needed.
    • The isPermissionsGranted function iterates through the permission list and checks if any permission is denied.
    • The deniedPermission function finds the first permission that is denied.
    • The showAlert function displays an alert dialog requesting the permissions.
    • The requestPermissions function checks if an explanation is needed before requesting permissions and then requests all permissions using ActivityCompat.requestPermissions.
    • The processPermissionsResult function processes the permission request result and returns true if all permissions are granted, false otherwise.
  • Layout and Manifest Files:

    • The activity_main.xml file defines the layout for the main activity which includes a button to trigger the permission request.
    • The AndroidManifest.xml file declares all the permissions the app needs in the <uses-permission> tags. Note that some of the declared permissions (like INTERNET) are not requested at runtime in this example.

Summary

This code provides a well-structured example of requesting permissions at runtime in an Android Kotlin application. It separates the permission handling logic into a dedicated class for better organization and reusability. The code also includes explanations for each step of the process, making it easier to understand and adapt for different permission scenarios in your own Android applications.


MainActivity.kt

package com.cfsuman.kotlinexamples

import android.Manifest
import android.content.Context
import android.os.Build
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {
    private val PermissionsRequestCode = 123
    private lateinit var managePermissions: ManagePermissions

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initialize a list of required permissions to request runtime
        val list = listOf<String>(
                Manifest.permission.CAMERA,
                Manifest.permission.READ_CONTACTS,
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.SEND_SMS,
                Manifest.permission.READ_CALENDAR
        )

        // Initialize a new instance of ManagePermissions class
        managePermissions = ManagePermissions(this,list,PermissionsRequestCode)

        // Button to check permissions states
        button.setOnClickListener{
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
                managePermissions.checkPermissions()
        }
    }


    // Receive the permissions request result
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
                                            grantResults: IntArray) {
        when (requestCode) {
            PermissionsRequestCode ->{
                val isPermissionsGranted = managePermissions
                        .processPermissionsResult(requestCode,permissions,grantResults)

                if(isPermissionsGranted){
                    // Do the task now
                    toast("Permissions granted.")
                }else{
                    toast("Permissions denied.")
                }
                return
            }
        }
    }
}


// Extension function to show toast message
fun Context.toast(message: String) {
    Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
ManagePermissions.kt

package com.cfsuman.kotlinexamples

import android.app.Activity
import android.content.pm.PackageManager
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog


class ManagePermissions(val activity: Activity,val list: List<String>,val code:Int) {

    // Check permissions at runtime
    fun checkPermissions() {
        if (isPermissionsGranted() != PackageManager.PERMISSION_GRANTED) {
            showAlert()
        } else {
            activity.toast("Permissions already granted.")
        }
    }


    // Check permissions status
    private fun isPermissionsGranted(): Int {
        // PERMISSION_GRANTED : Constant Value: 0
        // PERMISSION_DENIED : Constant Value: -1
        var counter = 0;
        for (permission in list) {
            counter += ContextCompat.checkSelfPermission(activity, permission)
        }
        return counter
    }


    // Find the first denied permission
    private fun deniedPermission(): String {
        for (permission in list) {
            if (ContextCompat.checkSelfPermission(activity, permission)
                    == PackageManager.PERMISSION_DENIED) return permission
        }
        return ""
    }


    // Show alert dialog to request permissions
    private fun showAlert() {
        val builder = AlertDialog.Builder(activity)
        builder.setTitle("Need permission(s)")
        builder.setMessage("Some permissions are required to do the task.")
        builder.setPositiveButton("OK", { dialog, which -> requestPermissions() })
        builder.setNeutralButton("Cancel", null)
        val dialog = builder.create()
        dialog.show()
    }


    // Request the permissions at run time
    private fun requestPermissions() {
        val permission = deniedPermission()
        if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
            // Show an explanation asynchronously
            activity.toast("Should show an explanation.")
        } else {
            ActivityCompat.requestPermissions(activity, list.toTypedArray(), code)
        }
    }


    // Process permissions result
    fun processPermissionsResult(requestCode: Int, permissions: Array<String>,
                                 grantResults: IntArray): Boolean {
        var result = 0
        if (grantResults.isNotEmpty()) {
            for (item in grantResults) {
                result += item
            }
        }
        if (result == PackageManager.PERMISSION_GRANTED) return true
        return false
    }
}
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#92a692"
    android:padding="16dp"
    android:orientation="vertical"
    >
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Request Permissions At Runtime"
        android:textAllCaps="false"
        />
</LinearLayout>
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cfsuman.kotlinexamples"
    >

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <uses-permission android:name="android.permission.READ_CALENDAR"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>