How To Convert Your Favorite Website To An Android App In Minutes
When I travel, I frequently use my phone or tablet to read content, and having a mobile app for this purpose can make a significant difference in my experience.
I have been studying on educative.io for a few weeks and was surprised to find no official mobile application for this amazing platform.
So I decided to create my own using an Android WebView.
A few words about Educative
Educative.io is an online learning platform that provides interactive courses in software development, machine learning, data science, and other tech-related fields.
I am not sponsored by or have any kind of partnership with Educative.
What is a WebView?
In a nutshell, a WebView is a component used in mobile app development that displays web pages within an app’s interface. It is useful for displaying content that does not require integrations with your device but needs to be displayed on it.
In addition, it can be customized to your needs (style properties, screen size…) and interfaces can be added to call Android APIs from within the app using JavaScript.
Android App Architecture Overview
Detailed app architecture and best practices can be found here.
Creating a project
Requirements:
- Android Studio
- Java 11+
- Gradle 7+
Let’s get our hands dirty and create the WebView.
Adding the WebView component
In Android Studio:
- New Project > Empty Activity
- Enter the app name
- Enter the package name
- Select the language (we will use Kotlin for this post)
- Select the minimum API version
- Click on Create.
Minimal Setup
Add the following code to the activity_main.yml
(in app/res/layout
):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
RelativeLayout documentation here.
Replace the existing code by the following in MainActivity.java
:
class MainActivity : AppCompatActivity() {
private lateinit var myWebView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myWebView = findViewById(R.id.webview)
myWebView.webViewClient = WebViewClient()
myWebView.loadUrl(URL)
}
}
This code will load the given url when the app starts.
Last but not the least, you need to authorize the app to access to the Internet by adding the permission to
the AndroidManifest.xml
file (in app/manifests
):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
...
After these steps, you may be able to launch your app, but may need to add some settings to improve your experience.
Optimizing the application
If JavaScript is required, you need to enable the myWebView.settings.javaScriptEnabled
property.
Moreover, I recommend to bind
the JavaScript code of the website to your app.
Here is the MainActivity
class after the update:
class MainActivity : AppCompatActivity() {
private lateinit var myWebView: MyWebView
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myWebView = findViewById(R.id.webview)
myWebView.webViewClient = WebViewClient()
setSettings()
myWebView.loadUrl(URL)
}
private fun setSettings() {
myWebView.settings.javaScriptEnabled = true
myWebView.addJavascriptInterface(WebAppInterface(this), "Android")
}
class WebAppInterface(private val mContext: Context) {
@JavascriptInterface
fun showToast(toast: String) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
}
}
}
You might want to go back to your history using the back button.
You can override the onKeyDown
method to add this feature.
class MainActivity : AppCompatActivity() {
private lateinit var myWebView: MyWebView
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myWebView = findViewById(R.id.webview)
myWebView.webViewClient = WebViewClient()
setSettings()
myWebView.loadUrl(URL)
}
private fun setSettings() {
myWebView.settings.javaScriptEnabled = true
myWebView.addJavascriptInterface(WebAppInterface(this), "Android")
}
private class WebAppInterface(private val mContext: Context) {
@JavascriptInterface
fun showToast(toast: String) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK && myWebView.canGoBack()) {
myWebView.goBack()
return true
}
return super.onKeyDown(keyCode, event)
}
}
Solutions for rendering a web page properly
Dom storage
It may be necessary to enable the dom storage to properly render the desired website.
It is possible to enable it with the myWebView.settings.domStorageEnabled
property.
Render once ready
To ensure that a web page is displayed once it is available, you can create a WebApp
subclass and override
the invalidate
method.
Cache settings
The cache mode can be updated as needed. The default value is LOAD_DEFAULT
(use cached resources when they are
available and not expired, otherwise load resources from the network).
package com.ths83.educative
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.util.AttributeSet
import android.view.KeyEvent
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
private const val URL = "https://www.educative.io"
class MainActivity : AppCompatActivity() {
private lateinit var myWebView: MyWebView
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myWebView = findViewById(R.id.webview)
myWebView.webViewClient = WebViewClient()
setSettings()
myWebView.loadUrl(URL)
}
private fun setSettings() {
myWebView.settings.javaScriptEnabled = true
myWebView.addJavascriptInterface(WebAppInterface(this), "Android")
myWebView.settings.builtInZoomControls = false
myWebView.settings.domStorageEnabled = true
myWebView.settings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK && myWebView.canGoBack()) {
myWebView.goBack()
return true
}
return super.onKeyDown(keyCode, event)
}
private class WebAppInterface(private val mContext: Context) {
@JavascriptInterface
fun showToast(toast: String) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
}
}
class MyWebView(context: Context, attrs: AttributeSet?) : WebView(context, attrs) {
override fun invalidate() {
super.invalidate()
// Display if fully downloaded and ready to render
if (progress == 100 && contentHeight > 0) {
// WebView has displayed some content and is scrollable.
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<view class="com.ths83.educative.MainActivity$MyWebView"
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
Interesting settings to look at
In AndroidManifest.xml
:
Opt out from Google’s metrics:
<meta-data
android:name="android.webkit.WebView.MetricsOptOut"
android:value="true"/>
Disable safe browsing warnings:
<meta-data
android:name="android.webkit.WebView.EnableSafeBrowsing"
android:value="false"/>
Consider those drawbacks
-
Storage: Limited storage space can restrict the amount of data that an app can store locally, which can impact its functionality and user experience. Apps should be designed to use storage space efficiently and allow users to manage stored data.
-
Network: The reliability and speed of mobile networks can vary widely, which can impact the performance and usability of apps that rely on network connectivity. Apps should be designed to gracefully handle network connectivity issues and provide a good user experience even when network conditions are poor.
-
Battery life: Mobile devices have limited battery life, and power-hungry apps can quickly drain a device’s battery. Apps should be designed to use power efficiently and minimize battery drain, especially for background processes and features that are not essential to the app’s core functionality.
-
Rendering: Mobile devices have different screen sizes, resolutions, and aspect ratios, which can affect the way apps are displayed. Apps should be designed to use responsive layouts and scalable graphics to ensure that they are displayed correctly on different devices. Additionally, apps should be optimized for performance and rendering speed to provide a smooth user experience.
Outro
You can take a look at the GitHub’s repo for this post here.
And this is it for the post! Thanks for reading and stay tuned!
Useful links
- What is web-based content?
- Best practices
- Optimize page rendering
- Safe Browsing
- Android Metrics
- Wasm
- Enable dark theme
- Android tutorials