Skip to content

Instantly share code, notes, and snippets.

@mlagerberg
Last active August 17, 2018 15:40
Show Gist options
  • Save mlagerberg/b3d19685c8c021fa5ee6 to your computer and use it in GitHub Desktop.
Save mlagerberg/b3d19685c8c021fa5ee6 to your computer and use it in GitHub Desktop.
[WebFragment] Fragment with WebView, busy indicator, custom user-agent, and correct fallback when offline. SDK 9 and up #android
<?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"
android:paddingTop="?attr/actionBarSize">
<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>
package com.github.gist.monkeyinmysoup;
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.github.gist.monkeyinmysoup.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();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment