Android WebView with Bookmarks

Filed Under: Android

To display a web page as the part of the application we use android WebView in our application. We’ve covered the basics of WebView here. In this tutorial, we’ll implement a loading ProgressBar with a WebView and also allow bookmarking URLs for later viewing. Let’s get started.

Android WebView

Android WebView class is an extension of Android’s View class that allows you to display web pages as a part of your activity layout. To load an external page we invoke the method loadUrl(String url) on the WebView instance and pass in the url of the external page. The WebViewClient contains the following four important methods that are generally overridden.

  1. onPageStarted : As the name suggests, this method gets invoked when the url loading starts.
  2. shouldOverrideUrlLoading : This method is called whenever an internal link from an already loaded page is clicked. For API>24 shouldOverrideUrlLoading(WebView view, String url) is deprecated, use shouldOverrideUrlLoading(WebView view, WebResourceRequest request) instead.
  3. onPageFinished : When the url is loaded completely and successfully, this gets invoked
  4. onReceivedError : When the url isn’t loaded, this method gets invoked.

To enable zoom controls on the webview we can invoke the following methods on the webView instance.


webView.getSettings().setSupportZoom(true);
webView.getSettings().setBuiltInZoomControls(true); // allow pinch to zooom
webView.getSettings().setDisplayZoomControls(false); // disable the default zoom controls on the page

Let’s create an application that loads a web page whilst showing the ProgressBar. We’ll add a functionality that lets us bookmark a URL and save it in our SharedPreferences for later viewing.

Android WebView with Bookmark Project Structure

android webview with bookmarks

We’ve selected the Activity type as Navigation Drawer.

android studio navigation drawer

Note: If you’ve updated your build tools to API 26 and are experiencing an error : “Failed to resolve com.android.support:appcompat-v7:26.0.1 “, you need to add Google’s Maven Repository in the build.gradle file as shown below:


apply plugin: 'com.android.application'

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    }
}


android {
    compileSdkVersion 26
    buildToolsVersion "26.0.1"
    defaultConfig {
        applicationId "com.journaldev.webviewwithbookmarks"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.0.1'
    compile 'com.android.support:design:26.0.1'
    compile 'com.google.code.gson:gson:2.7'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
}

Note: The Gson library dependency for saving bookmarked urls in Shared Preferences is also added above.

Android WebView Bookmarks Code

The code of the activity_main.xml layout is given below:


<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer" />

</android.support.v4.widget.DrawerLayout>

The code for the app_bar_main.xml layout is given below:


<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.journaldev.webviewwithbookmarks.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

</android.support.design.widget.CoordinatorLayout>

The layout for the content_main.xml is given below. It contains a Button that’ll be used to launch another activity.


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.journaldev.webviewwithbookmarks.MainActivity"
    tools:showIn="@layout/app_bar_main">

    <Button
        android:id="@+id/btnLaunchWebsite"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="LAUNCH WEBSITE"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

The code for the MainActivity.java is given below:


package com.journaldev.webviewwithbookmarks;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;

public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {

    Button button;
    NavigationView navigationView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        DrawerLayout drawer = findViewById(R.id.drawer_layout);

        button = findViewById(R.id.btnLaunchWebsite);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                gotoBrowserActivity();
            }
        });


        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        navigationView = findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        int id = item.getItemId();

        if (id == R.id.nav_home) {
            navigationView.getMenu().getItem(0).setChecked(false);
        } else if (id == R.id.nav_bookmark) {
            navigationView.getMenu().getItem(1).setChecked(false);
            startActivity(new Intent(this, BookmarkActivity.class));
        }
        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }

    private void gotoBrowserActivity() {
        startActivity(new Intent(this, BrowserActivity.class));
    }
}

In the above code we’ve defined two menu options(the file resides inside the menu folder as activity_main_drawer.xml) inside the NavigationDrawer.

Clicking the Button in the MainActivity.java would launch the BrowserActivity.java and clicking the Bookmark menu button would launch the BookmarkActivity.java that we’ll see shortly.

Add the following permission to access internet in your AndroidManifest.xml.


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

The code for the activity_browser.xml is given below.


<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView 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:fadeScrollbars="false"
        android:scrollbarFadeDuration="0"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">


        <WebView
            android:id="@+id/webView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />


    </android.support.v4.widget.NestedScrollView>

    <ProgressBar
        android:id="@+id/progressBar"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="-7dp"
        android:indeterminate="true"
        android:visibility="gone"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />


</android.support.design.widget.CoordinatorLayout>

The code for the BrowserActivity.java is given below:


package com.journaldev.webviewwithbookmarks;

import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.util.ArrayList;

public class BrowserActivity extends AppCompatActivity {


    public static final String PREFERENCES = "PREFERENCES_NAME";
    public static final String WEB_LINKS = "links";
    public static final String WEB_TITLE = "title";

    WebView webView;
    private ProgressBar progressBar;
    String current_page_url = "http://www.wikipedia.com";
    CoordinatorLayout coordinatorLayout;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_browser);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setTitle("");
        toolbar.setNavigationIcon(R.drawable.ic_arrow_back);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onBackPressed();
            }
        });

        if (getIntent().getExtras() != null) {
            current_page_url = getIntent().getStringExtra("url");
        }

        webView = findViewById(R.id.webView);
        progressBar = findViewById(R.id.progressBar);
        webView.loadUrl(current_page_url);
        initWebView();

        coordinatorLayout = findViewById(R.id.main_content);
    }

    private void initWebView() {
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
                progressBar.setVisibility(View.VISIBLE);
                current_page_url = url;
                invalidateOptionsMenu();
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                webView.loadUrl(url);
                return true;
            }
            
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    webView.loadUrl(request.getUrl().toString());
                }
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                progressBar.setVisibility(View.GONE);
                invalidateOptionsMenu();
            }

            @Override
            public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
                super.onReceivedError(view, request, error);
                progressBar.setVisibility(View.GONE);
                invalidateOptionsMenu();
            }
        });

        webView.getSettings().setLoadWithOverviewMode(true);
        webView.getSettings().setUseWideViewPort(true);
        webView.clearCache(true);
        webView.clearHistory();
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setHorizontalScrollBarEnabled(true);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.browser, menu);

        SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
        String links = sharedPreferences.getString(WEB_LINKS, null);

        if (links != null) {

            Gson gson = new Gson();
            ArrayList<String> linkList = gson.fromJson(links, new TypeToken<ArrayList<String>>() {
            }.getType());

            if (linkList.contains(current_page_url)) {
                menu.getItem(0).setIcon(R.drawable.ic_bookmark_black_24dp);
            } else {
                menu.getItem(0).setIcon(R.drawable.ic_bookmark_border_black_24dp);
            }
        } else {
            menu.getItem(0).setIcon(R.drawable.ic_bookmark_border_black_24dp);
        }
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        if (item.getItemId() == R.id.action_bookmark) {

            String message;

            SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
            String jsonLink = sharedPreferences.getString(WEB_LINKS, null);
            String jsonTitle = sharedPreferences.getString(WEB_TITLE, null);


            if (jsonLink != null && jsonTitle != null) {

                Gson gson = new Gson();
                ArrayList<String> linkList = gson.fromJson(jsonLink, new TypeToken<ArrayList<String>>() {
                }.getType());

                ArrayList<String> titleList = gson.fromJson(jsonTitle, new TypeToken<ArrayList<String>>() {
                }.getType());

                if (linkList.contains(current_page_url)) {
                    linkList.remove(current_page_url);
                    titleList.remove(webView.getTitle().trim());
                    SharedPreferences.Editor editor = sharedPreferences.edit();
                    editor.putString(WEB_LINKS, new Gson().toJson(linkList));
                    editor.putString(WEB_TITLE, new Gson().toJson(titleList));
                    editor.apply();


                    message = "Bookmark Removed";

                } else {
                    linkList.add(current_page_url);
                    titleList.add(webView.getTitle().trim());
                    SharedPreferences.Editor editor = sharedPreferences.edit();
                    editor.putString(WEB_LINKS, new Gson().toJson(linkList));
                    editor.putString(WEB_TITLE, new Gson().toJson(titleList));
                    editor.apply();

                    message = "Bookmarked";
                }
            } else {

                ArrayList<String> linkList = new ArrayList<>();
                ArrayList<String> titleList = new ArrayList<>();
                linkList.add(current_page_url);
                titleList.add(webView.getTitle());
                SharedPreferences.Editor editor = sharedPreferences.edit();
                editor.putString(WEB_LINKS, new Gson().toJson(linkList));
                editor.putString(WEB_TITLE, new Gson().toJson(titleList));
                editor.apply();

                message = "Bookmarked";
            }

            Snackbar snackbar = Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG);
            snackbar.show();

            invalidateOptionsMenu();
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        if (webView.canGoBack()) {
            webView.goBack();
        } else {
            super.onBackPressed();
        }
    }

}

In the above code, we load the url http://www.wikipedia.com in the WebView.

We display and hide the ProgressBar when the url is loading and completed respectively.

The menu is inflated from the browser.xml file as shown below.


<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_bookmark"
        android:icon="@drawable/ic_bookmark_black_24dp"
        android:orderInCategory="100"
        android:title="BOOKMARK"
        app:showAsAction="always" />

</menu>

In the onCreateOptionsMenu() we check if the current_page_url already exists in our SharedPreferences or not. Depending on the outcome, we show the relevant bookmark menu icon.

In the onOptionsItemSelected() we store or remove the url from the SharedPreferences depending on whether it exists or not.

The SharedPreferences stores the ArrayList of links and the respective web page titles, in the form of Gson strings that’ll be eventually displayed in the BookmarkActivity.java which we’ll discuss below.

invalidateOptionsMenu() is used to redraw the menu in the Toolbar.

onBackPressed() is used to navigate back through the web page if the user had clicked any of the internal links in the WebView by checking and returning using canGoBack() and goBack().

The code for activity_bookmark.xml is given below.


<RelativeLayout 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.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_alignParentTop="true"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/AppTheme.PopupOverlay" />

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipeToRefresh"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/toolbar"
        android:layout_margin="@dimen/fab_margin">

        <ListView
            android:id="@+id/listView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.v4.widget.SwipeRefreshLayout>

    <LinearLayout
        android:id="@+id/emptyList"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:orientation="vertical"
        android:visibility="gone">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center"
            android:text="WHOOPS"
            android:textColor="#212121"
            android:textSize="20sp"
            android:textStyle="bold" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center"
            android:text="There are no bookmarks at the moment"
            android:textColor="#212121" />


    </LinearLayout>


</RelativeLayout>

The code for the BookmarkActivity.java is given below.


package com.journaldev.webviewwithbookmarks;

import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import static com.journaldev.webviewwithbookmarks.BrowserActivity.PREFERENCES;
import static com.journaldev.webviewwithbookmarks.BrowserActivity.WEB_LINKS;
import static com.journaldev.webviewwithbookmarks.BrowserActivity.WEB_TITLE;

public class BookmarkActivity extends AppCompatActivity {

    ArrayList<HashMap<String, String>> listRowData;

    public static String TAG_TITLE = "title";
    public static String TAG_LINK = "link";

    ListView listView;
    ListAdapter adapter;
    LinearLayout linearLayout;
    SwipeRefreshLayout mSwipeRefreshLayout;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bookmark);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setTitle("BOOKMARKS");
        toolbar.setNavigationIcon(R.drawable.ic_arrow_back);
        toolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onBackPressed();
            }
        });

        listView = findViewById(R.id.listView);
        linearLayout = findViewById(R.id.emptyList);

        mSwipeRefreshLayout = findViewById(R.id.swipeToRefresh);
        mSwipeRefreshLayout.setColorSchemeResources(R.color.colorAccent);
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                new LoadBookmarks().execute();

            }
        });

        new LoadBookmarks().execute();
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            public void onItemClick(AdapterView<?> parent, View view,
                                    int position, long id) {

                Object o = listView.getAdapter().getItem(position);
                if (o instanceof Map) {
                    Map map = (Map) o;
                    Intent in = new Intent(BookmarkActivity.this, BrowserActivity.class);
                    in.putExtra("url", String.valueOf(map.get(TAG_LINK)));
                    startActivity(in);
                }


            }
        });

        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
                Object o = listView.getAdapter().getItem(i);
                if (o instanceof Map) {
                    Map map = (Map) o;
                    deleteBookmark(String.valueOf(map.get(TAG_TITLE)), String.valueOf(map.get(TAG_LINK)));
                }

                return true;
            }
        });

    }

    private class LoadBookmarks extends AsyncTask<String, String, String> {


        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected String doInBackground(String... args) {
            // updating UI from Background Thread
            runOnUiThread(new Runnable() {
                public void run() {

                    SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
                    String jsonLink = sharedPreferences.getString(WEB_LINKS, null);
                    String jsonTitle = sharedPreferences.getString(WEB_TITLE, null);
                    listRowData = new ArrayList<>();

                    if (jsonLink != null && jsonTitle != null) {

                        Gson gson = new Gson();
                        ArrayList<String> linkArray = gson.fromJson(jsonLink, new TypeToken<ArrayList<String>>() {
                        }.getType());

                        ArrayList<String> titleArray = gson.fromJson(jsonTitle, new TypeToken<ArrayList<String>>() {
                        }.getType());


                        for (int i = 0; i < linkArray.size(); i++) {
                            HashMap<String, String> map = new HashMap<>();

                            if (titleArray.get(i).length() == 0)
                                map.put(TAG_TITLE, "Bookmark " + (i + 1));
                            else
                                map.put(TAG_TITLE, titleArray.get(i));

                            map.put(TAG_LINK, linkArray.get(i));
                            listRowData.add(map);
                        }

                        adapter = new SimpleAdapter(BookmarkActivity.this,
                                listRowData, R.layout.bookmark_list_row,
                                new String[]{TAG_TITLE, TAG_LINK},
                                new int[]{R.id.title, R.id.link});

                        listView.setAdapter(adapter);
                    }

                    linearLayout.setVisibility(View.VISIBLE);
                    listView.setEmptyView(linearLayout);


                }
            });
            return null;
        }

        protected void onPostExecute(String args) {
            mSwipeRefreshLayout.setRefreshing(false);
        }

    }

    private void deleteBookmark(final String title, final String link) {

        new AlertDialog.Builder(this)
                .setTitle("DELETE")
                .setMessage("Confirm that you want to delete this bookmark?")
                .setPositiveButton("YES", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
                        String jsonLink = sharedPreferences.getString(WEB_LINKS, null);
                        String jsonTitle = sharedPreferences.getString(WEB_TITLE, null);


                        if (jsonLink != null && jsonTitle != null) {


                            Gson gson = new Gson();
                            ArrayList<String> linkArray = gson.fromJson(jsonLink, new TypeToken<ArrayList<String>>() {
                            }.getType());

                            ArrayList<String> titleArray = gson.fromJson(jsonTitle, new TypeToken<ArrayList<String>>() {
                            }.getType());


                            linkArray.remove(link);
                            titleArray.remove(title);


                            SharedPreferences.Editor editor = sharedPreferences.edit();
                            editor.putString(WEB_LINKS, new Gson().toJson(linkArray));
                            editor.putString(WEB_TITLE, new Gson().toJson(titleArray));
                            editor.apply();

                            new LoadBookmarks().execute();
                        }
                        dialogInterface.dismiss();
                    }
                }).setNegativeButton("NO", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                dialogInterface.dismiss();
            }
        }).show();
    }

}

In the above code we deserialise the strings from SharedPreferences using Gson and convert them into the respective links and titles ArrayList of Strings inside the AsyncTask LoadBookmarks.

SimpleAdapter is a built-in adapter for the ListView. Its useful to map static data to views defined in an XML file.

The layout for the ListView rows is given below:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >

    <TextView
        android:id="@+id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:maxLines="1"
        android:paddingBottom="2dp"
        android:paddingTop="4dp"
        android:textColor="#000"
        android:textSize="16sp" />


    <TextView
        android:id="@+id/link"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:maxLines="1"
        android:layout_below="@+id/title"
        android:paddingBottom="4dp"
        android:paddingTop="2dp"
        android:textSize="14sp" />


</RelativeLayout>

setOnItemLongClickListener() is invoked to long press to delete a bookmark. Returning a false in it would call setOnItemClickListener() at the same time too, hence its recommended to return true.

The output that the application gives is shown below.

android webview bookmarks example app

This brings an end to this tutorial. You can download the final Android WebViewWithBookmarks Project from the link below.

References: WebView, Simple Adapter

Comments

  1. Kapil dev says:

    Hello sir
    I have API 24 level, targetSdkVersion 28 also add google maven repositories when click on Launch and bookmark button the ‘keeps stopped’ plz help how to resolve it?

    1. Anupam says:

      You need to check the logs and the error stack trace you are getting.

      1. kapil dev says:

        thanku sir.

        how can i implement with 2-3 buttons getting link from values>string.xml file.

  2. Justin says:

    Thanks for the great tutorial. I have already created an almost full featured web browser, now I’m putting the last few final touches on it. Two of the major features my browser lacks are bookmarks and unlimited tabbed browsing. You’d be surprised at the lack of documentation/tutorials on these features. I’ve been all the way to the edge of the Google universe and back and found nothing.Not just Google, there isn’t anything thats at all comprehensive on YouTube either. I’m not finished with the bookmarks yet because its been really difficult trying to incorporate this code into an already pretty well established web browser. Also, I’m a beginner as well. Some of the details here seem a little vague for a newbie so thats been another challenge. I really don’t understand how gson and shared preferences work. So I guess the biggest question for now is; do I have to move to your next tutorial and add a new activity and add all the code for the shared preferences, or is that all working in the background with the dependencies. Or, will just this code work? I just don’t get it. Please help. Just a little nudge in the right direction and I would be forever grateful. Thanks. Also, do you have any, or know where to get any comprehensive tutorials on unlimited tabbed browsing? I don’t want fragment tabs, theres a lot of stuff out there about that, but thats useless. There is literally nothing out there on tabs(TDI). And trying to look at stuff on github is useless as the code is scattered all about and I have no idea how to put it all together. Thanks again sir.

  3. Warren says:

    Just a quick fyi:

    * I was getting a “resource not found” error in logcat to explain why the build apk was being force closed (on an old Kit Kat device).

    * The simple work around was to move the contents of “app/src/main/res/drawable-v21” into “drawable” (and then delete the no-longer used “drawable-v21” directory); afterward, the build apk works as expected.

    1. Anupam says:

      Yes Warren,
      Resources present in drawable-21 are meant for Android L and above.
      To use resources in Pre-Lollipop use the drawable folder.

  4. Mukesh says:

    Hello sir

    Plz Add one More Button to Launch Website and open in same Browser Activity

    Plz Sir Reply

    i m Confuse i am create new activity but App was Unfortunately Stop issue toolbar
    launch activity new

    Anyone Read this Comment Plz Reply with Solution

    Waiting for Reply

    Thank You!

  5. Mukesh says:

    Hello Sir

    i am Create a New Activity and set goto–activity in mainActivity and implement all code browser activity but not work how to create a new button and Goto Second Activity

    plz sir Reply

  6. Mukesh Yadav says:

    And Bookmark Was Not Work Offline …

    Please Send me Code Offline Feature..

    Thank You!

  7. Mukesh Yadav says:

    Hello sir,

    Please Help Android Jellybean 4.1 Not Support 16 Api Level Uninformative Stop.. Problem.. and Upper Version Work Perfectly…

    Please Reply

  8. Rana says:

    I have only one activity of my webview application, that is Main activity I want to add bookmark option to this Main activity. How can I do this. I tried many times but failed to do it. I’m beginner, so please could you please give me example code like this tutorial. So that I can add bookmark system on main activity.

Leave a Reply

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

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages