Fragment with a WebView
and fall-back UI in case there’s no active data connection.
Note: if you plan to open external websites, we suggest to use the Chrome Custom Tabs instead. The snippets below tie in more with your own app, and are perfect for, e.g. showing terms & conditions and privacy policies.
To open a url in a custom tab, use the Kotlin function here.
Features:
- Compatible with Android SDK 9 and up.
- Sets the UserAgent string (e.g:
AppName/1.0-5 (Android 6.0-23) - LGE Nexus 5
). - Configures caching and localStorage.
- Adds a progressbar to the top of the screen.
- Shows a placeholder UI when there is no connection, with buttons to launch the settings or to try again.
Usage:
- Copy both files (
WebFragment.java
andfragment_web.xml
) to the right folders. - Simply place the Fragment in the desired container, or use the FragmentActivity.
- A url is expected in
R.string.web_url
. - Disables Javascript by default, turn on by modifying the
ENABLE_JAVASCRIPT
constant. - Requires adding several strings and icons to the resources – just follow the errors.
WebFragment.java
package com.pixplicity.example.ui;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import com.pixplicity.example.R;
/**
* Fragment with a WebView and fall-back UI in case there's no active data connection.
* A url is expected in R.string.web_url.
* <p/>
* Compatible with Android SDK 9 and up.
*/
public class WebFragment extends Fragment {
// Only set to true if you know it will be safe
private static final boolean ENABLE_JAVASCRIPT = false;
// Max app cache size. Ignored on SDK 18+
private static final long APP_CACHE_SIZE = 5 * 1024 * 1024;
// Set to false to open all links in the webview as well.
// Otherwise only links within the same domain as the starting url will open in the webview.
private static final boolean OPEN_LINKS_IN_BROWSER = true;
private static final boolean ALLOW_LOCAL_STORAGE = true;
/**
* User-Agent string. E.g. "AppName/1.0-5 (Android 6.0-23) - LGE Nexus 5"
*/
private static final String USER_AGENT = "%1$s/%2$s-%3$s (Android %4$s-%5$s) - %6$s %7$s";
private static final String TAG = WebFragment.class.getSimpleName();
private boolean mIsWebViewAvailable;
private boolean mHasFailed = false;
private String mWebUrl;
private String mWebDomain;
private WebView mWebView;
private ViewGroup mRootView;
private ProgressBar mProgress;
public WebFragment() {
}
public static WebFragment getInstance() {
return new WebFragment();
}
@SuppressLint("SetJavaScriptEnabled")
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate
mRootView = (ViewGroup) inflater.inflate(
R.layout.fragment_web, container, false);
mWebView = (WebView) mRootView.findViewById(R.id.webview);
mProgress = (ProgressBar) mRootView.findViewById(R.id.pb_web);
mWebUrl = getString(R.string.web_url);
mWebDomain = getDomain(mWebUrl);
// Retry and Settings buttons
mRootView.findViewById(R.id.bt_refresh).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHasFailed = false;
mWebView.loadUrl(mWebUrl);
}
});
mRootView.findViewById(R.id.bt_settings).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
startActivity(intent);
}
});
// Allow local storage if needed
if (ALLOW_LOCAL_STORAGE) {
String databasePath = getActivity().getDir("databases", Context.MODE_PRIVATE).getPath();
mWebView.getSettings().setDatabasePath(databasePath);
mWebView.getSettings().setDatabaseEnabled(true);
}
mIsWebViewAvailable = true;
// Custom User Agent
WebSettings settings = mWebView.getSettings();
settings.setUserAgentString(getUserAgent());
// Local caching for offline use
settings.setAppCacheMaxSize(APP_CACHE_SIZE);
settings.setAppCachePath(getActivity().getApplicationContext().getCacheDir().getAbsolutePath());
settings.setAllowFileAccess(true);
settings.setAppCacheEnabled(true);
settings.setJavaScriptEnabled(ENABLE_JAVASCRIPT);
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
if (!hasConnection()) {
settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
// Open links in default browser and show offline message when needed
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
mHasFailed = false;
//noinspection PointlessBooleanExpression
if (OPEN_LINKS_IN_BROWSER && !isSameDomain(mWebDomain, url)) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(browserIntent);
return true;
}
return false;
}
@Override
public void onPageFinished(WebView view, String url) {
if (!mHasFailed) {
// Hide offline message (if any)
mWebView.setVisibility(View.VISIBLE);
mRootView.findViewById(R.id.vg_offline).setVisibility(View.GONE);
}
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// Check if we can reach the settings:
final Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
List<ResolveInfo> list = getActivity().getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() == 0) {
// No? Hide the settings button and divider
mRootView.findViewById(R.id.divider).setVisibility(View.GONE);
mRootView.findViewById(R.id.bt_settings).setVisibility(View.GONE);
}
// Show message:
mRootView.findViewById(R.id.vg_offline).setVisibility(View.VISIBLE);
mWebView.setVisibility(View.GONE);
mHasFailed = true;
}
});
// Configure progress bar
mWebView.setWebChromeClient(
new WebChromeClient() {
public void onProgressChanged(WebView view, int progress) {
if (progress < 100) {
mProgress.setVisibility(View.VISIBLE);
mProgress.setProgress(progress);
} else {
mProgress.setVisibility(View.GONE);
}
}
});
// Go!
mWebView.loadUrl(mWebUrl);
return mRootView;
}
/**
* Checks if the given url is in the same domain as the given domain
*
* @param domain The domain
* @param url The url
* @return
*/
private boolean isSameDomain(String domain, String url) {
return domain != null && domain.equals(getDomain(url));
}
/**
* Returns the domain part of a url
*
* @param url
* @return
*/
private String getDomain(String url) {
try {
return new URL(url).getHost();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
/**
* Creates a custom user-agent string in the shape of {@link #USER_AGENT}.
*
* @return A string, suitable to be used as the user-agent in the webview
*/
protected String getUserAgent() {
int versionCode = 0;
String versionName = "";
try {
versionCode = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0).versionCode;
versionName = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0).versionName;
} catch (NameNotFoundException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
String appName = getString(R.string.app_name);
return String.format(USER_AGENT,
appName,
versionName,
versionCode,
Build.VERSION.RELEASE,
Build.VERSION.SDK_INT,
Build.MANUFACTURER,
Build.MODEL);
}
/**
* Checks if there's an active data connection. Does not bother verifying the connection, or to
* differentiate between WiFi or metered connection.
*
* @return {@code true} if the page can probably be loaded.
*/
public boolean hasConnection() {
ConnectivityManager man = (ConnectivityManager)
getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
return man.getActiveNetworkInfo() != null;
}
@Override
public void onPause() {
super.onPause();
mWebView.onPause();
}
@Override
public void onResume() {
if (mIsWebViewAvailable) {
mWebView.onResume();
}
super.onResume();
}
@Override
public void onDestroy() {
mIsWebViewAvailable = false;
if (mWebView != null) {
mWebView.destroy();
mWebView = null;
}
super.onDestroy();
}
}
fragment_web.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/vg_offline"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_horizontal_margin"
android:gravity="center"
android:text="@string/tv_no_connection" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<Button
android:id="@+id/bt_refresh"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_refresh"
android:drawableStart="@drawable/ic_refresh"
android:padding="@dimen/activity_horizontal_margin"
android:text="@string/bt_refresh" />
<View
android:id="@+id/divider"
android:layout_width="2dp"
android:layout_height="match_parent"
android:layout_margin="@dimen/activity_horizontal_margin"
android:background="@color/dark_gray" />
<ImageButton
android:id="@+id/bt_settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/bt_settings"
android:padding="@dimen/activity_horizontal_margin"
android:src="@drawable/ic_settings" />
</LinearLayout>
</LinearLayout>
<ProgressBar
android:id="@+id/pb_web"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-6dp"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>