Android Tutorial for GeckoView: Getting Started

In thus tutorial you’ll learn about GeckoView, an open source library that allows you to render web content on Android using the Gecko web engine. By Arturo Mejia.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Adding a Toolbar

Having to recompile each time you want to load a different site isn’t an ideal situation. In this section, you’ll focus on creating a toolbar that allows you to enter the URL you want to visit.

Instead of having the URL hard-coded, update the layout to add a Toolbar. First, wrap the GeckoView in activity_main.xml with a RelativeLayout. The file should now look like this:

<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/main"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">

  <org.mozilla.geckoview.GeckoView
    android:id="@+id/geckoview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

</RelativeLayout>

Here, you added a RelativeLayout that will allow you to control how the widgets layout. Now, you can add the Toolbar above the GeckoView widget.

Add this code inside the RelativeLayout and above the GeckoView:

 <android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?android:actionBarSize">

    <EditText
      android:id="@+id/location_view"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@android:color/transparent"
      android:hint="@string/location_hint"
      android:importantForAutofill="no"
      android:inputType="text|textUri"
      android:selectAllOnFocus="true"
      android:singleLine="true" />

  </android.support.v7.widget.Toolbar>

This Toolbar contains an EditText you’ll use to enter new URLs.

Last but not least, to make sure everything looks nice, add this property to the GeckoView to make sure it is rendered below the Toolbar:

android:layout_below="@id/toolbar"

With the layout set up, it’s time to connect the input of the EditText with the GeckoView.

Next, go to the MainActivity.kt file and then, add a private property just below of the geckoSession.

 private lateinit var urlEditText: EditText

You added a EditText to allow you to fetch the entered URL.

Then update the setupGeckoView() function replacing the hard-coded URL geckoSession.loadUri("about:buildconfig") with the two lines below:

 geckoSession.loadUri(INITIAL_URL)
 urlEditText.setText(INITIAL_URL)

Here, you updated the initial URL to a constant value. When the app starts it will load this URL. INITIAL_URL is a helper constant and you can see its value in Helpers.kt.

Next, continue updating the URL with the content of the EditText. Add the following method after onCreate, importing android.support.v7.app.ActionBar for the ActionBar:

  private fun setupToolbar() {
    setSupportActionBar(findViewById(R.id.toolbar))
    supportActionBar?.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM
  }

This indicates to the action bar that you are going to use a custom view, in this case the EditText.

Next, add the onCommit() function. You’ll call this when a new URL is entered:

  fun onCommit(text: String) {
    if ((text.contains(".") || 
            text.contains(":")) && 
        !text.contains(" ")) {
      geckoSession.loadUri(text)
    } else {
      geckoSession.loadUri(SEARCH_URI_BASE + text)
    }

    geckoView.requestFocus()
  }

Here you update the new URL in the GeckoView widget. If the text entered is an URL you load it. Otherwise, you trigger a DuckDuckGo search.

SEARCH_URI_BASE is a constant helper. It contains a parameterized url. You just add the search criteria by appending the text.

Then, add the setupUrlEditText() function to link it all together:

  private fun setupUrlEditText() {
    urlEditText = findViewById(R.id.location_view)

    urlEditText.setOnEditorActionListener(object : 
        View.OnFocusChangeListener, TextView.OnEditorActionListener {

      override fun onFocusChange(view: View?, hasFocus: Boolean) = Unit

      override fun onEditorAction(
          textView: TextView,
          actionId: Int,
          event: KeyEvent?
      ): Boolean {
        onCommit(textView.text.toString())
        textView.hideKeyboard()
        return true
      }
    })
  }

Here you add a listener for every time the user hits the enter key. This way it will know that the user wants to load the entered URL, and call onCommit() when this happens.

Finally, add calls to this set up. Inside the onCreate function, add these two new functions, setupUrlEditText() and setupToolbar(), right before the setupGeckoView().

 // 1
 setupToolbar()

 //2
 setupUrlEditText()

Here you:

  1. Set up the toolbar when the activity starts.
  2. Bind the EditText and set a listener for when a new URL is entered.

Phew, that’s all set up now. Build and run the App!

Now you’re able to enter the URL you want or perform a search with a word or a phrase.

AwesomeBrowser Demo

Adding a Progress Bar

Some pages take a long time to load and there’s no clear way to know if it’s loading or not. Has it finished or is still loading? Adding a toolbar will boost the user experience.

For this job, you need a ProgressBar and way to know which state a page is in. Fortunately, GeckoView provides an API for that. The ProgressDelegate is an interface for observing the progress of a web page.

The ProgressDelegate interface has different callbacks for the different statuses:

  • onPageStart: Called when the content has started loading.
  • onPageStop: Called when the content has finished loading.
  • onProgressChange: Called every time the progress of a web page has changed.
  • onSecurityChange: Indicates when the security status updates. It passes a SecurityInformation object that contains useful information about the site security.

Now, to put all that together in code.

First, go to the activity_main.xml file. Add a ProgressBar to the RelativeLayout, right after the Toolbar and before the GeckoView:

  <ProgressBar
    android:id="@+id/page_progress"
    style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
    android:layout_width="match_parent"
    android:layout_height="3dp"
    android:layout_alignBottom="@id/toolbar" />

This adds the ProgressBar at the bottom of the toolbar.

Next, add the ProgressBar to the MainActivity.kt file. First add a progressBar property below the urlEditText.

private lateinit var progressView: ProgressBar

This adds a private property to control the progress of the progress bar from different points in the code.

Next, add createProgressDelegate() to the end of MainActivity.

private fun createProgressDelegate(): GeckoSession.ProgressDelegate {
    return object : GeckoSession.ProgressDelegate {

      override fun onPageStop(session: GeckoSession, success: Boolean) = Unit

      override fun onSecurityChange(
          session: GeckoSession, 
          securityInfo: GeckoSession.ProgressDelegate.SecurityInformation
      ) = Unit

      override fun onPageStart(session: GeckoSession, url: String) = Unit

      override fun onProgressChange(session: GeckoSession, progress: Int) {
        progressView.progress = progress

        if (progress in 1..99) {
          progressView.visibility = View.VISIBLE
        } else {
          progressView.visibility = View.GONE
        }
      }
    }
  }

This is your implementation of ProgressDelegate. Since you’re only interested in changes in progress, onProgressChange() is the only function you’ve added an implementation for.

Inside it, you’re updating the progress bar depending on the progress of the page. You’re hiding or showing the progress bar depending on if the page has finished loading or not.

After that, at the end of setupGeckoView(), add these two lines.

 progressView = findViewById(R.id.page_progress)
 geckoSession.progressDelegate = createProgressDelegate()

Here, you add the ProgressDelegate. This is how the geckoSession allows you to listen for new updates in the progress of a web page.

Now build and run the app. You’ll see a the progress as the page loads.

AwesomeBrowser Demo

Tracking Protection

Have you ever felt that some ads follow you to whatever site you visit? For example, you were looking at a pair of shoes on an e-commerce site. Then, on every page you visit after that you’re harassed with ads for that same pair of shoes.

No, the shoes aren’t haunted and it’s not a coincidence. Many companies make a living tracking your preferences and behavior on the web and then selling it, without even you knowing.

Tracking protection, an amazing builit-in feature of GeckoView, stops these sites from following you. Time to see how you can integrate this feature into AwesomeBrowser.

GeckoView provides an API for enabling tracking protection and getting information about the trackers. ContentBlocking.Delegate is an interface that allows you to set a listener on a session and get notified when a content element is blocked from loading. It also provides you with useful data like the categories of the blocked content via a BlockEvent object.

Time to update the UI to show how many trackers have been blocked per site.

First, update activity_main.xml to add a TextView at the end of the RelativeLayout.

  <TextView
    android:id="@+id/trackers_count"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignEnd="@id/geckoview"
    android:layout_alignBottom="@id/geckoview"
    android:layout_margin="10dp"
    android:background="@drawable/circle_background"
    android:elevation="10dp"
    android:gravity="center"
    android:textColor="@android:color/white"
    android:textSize="15sp"
    android:textStyle="bold"
    android:text="0" />

trackers_count is a where you’re going to store how many trackers were blocked. When it’s pressed a pop-up window will appear showing all the details about the trackers.

Next, in MainActivity.kt add the logic for showing the number of trackers blocked and the individual details of each one. Don’t forget to import all the new references.

Add two properties, trackersCount and trackersBlockedList, after the progressView.

private lateinit var trackersCount: TextView
private var trackersBlockedList: List<ContentBlocking.BlockEvent> = 
    mutableListOf()

Here, you add two private properties, trackersCount and trackersBlockedList. These two contain how many trackers were blocked and the list of each of them.

Continue by adding a function to create the blocking delegate. Add createBlockingDelegate() to the end of the MainActivity class:

  private fun createBlockingDelegate(): ContentBlocking.Delegate {
    return object : ContentBlocking.Delegate {
      override fun onContentBlocked(session: GeckoSession, event: ContentBlocking.BlockEvent) {
        trackersBlockedList = trackersBlockedList + event
        trackersCount.text = "${trackersBlockedList.size}"
      }
    }
  }

Here you set a listener for notifying when content is blocked. Inside the listener, you’re updating the state of the trackersBlockedList, adding a new blocked content to the list and then updating the counter, trackersCount.

Now use your delegate by adding this to the end of setupGeckoView():

 geckoSession.settings.useTrackingProtection = true
 geckoSession.contentBlockingDelegate = createBlockingDelegate()

This enables tracking protection. So, you’re telling this session that tracking protection will be activated.

Now that you have the delegate set up, you can show the list of blocked URLs when the count is tapped.

Start by adding an extension function categoryToString() to categorize different blocked events:

  private fun ContentBlocking.BlockEvent.categoryToString(): String {
    val stringResource = when (categories) {
      ContentBlocking.NONE -> R.string.none
      ContentBlocking.AT_ANALYTIC -> R.string.analytic
      ContentBlocking.AT_AD -> R.string.ad
      ContentBlocking.AT_TEST -> R.string.test
      ContentBlocking.SB_MALWARE -> R.string.malware
      ContentBlocking.SB_UNWANTED -> R.string.unwanted
      ContentBlocking.AT_SOCIAL -> R.string.social
      ContentBlocking.AT_CONTENT -> R.string.content
      ContentBlocking.SB_HARMFUL -> R.string.harmful
      ContentBlocking.SB_PHISHING -> R.string.phishing
      else -> R.string.none
    }
    return getString(stringResource)
  }

Here you map every tracker category to a String one.

Then add getFriendlyTrackersUrls() to use this extension function and get a list of formatted URLS:

  private fun getFriendlyTrackersUrls(): List<Spanned> {
    return trackersBlockedList.map { blockEvent ->
      val host = Uri.parse(blockEvent.uri).host
      val category = blockEvent.categoryToString()
      Html.fromHtml(
          "<b><font color='#D55C7C'>[$category]</font></b> <br/> $host", 
          HtmlCompat.FROM_HTML_MODE_COMPACT
      )
    }
  }

Here’s what you did. You styled the tracker’s list, adding a highlighting color to each blocked entry.

Next, add setupTrackersCounter() below the previous function to show the blocked events:

private fun setupTrackersCounter() {
  // 1
  trackersCount = findViewById(R.id.trackers_count)
  trackersCount.text = "0"
  // 2
  trackersCount.setOnClickListener {
    if (trackersBlockedList.isNotEmpty()) {
      val friendlyURLs = getFriendlyTrackersUrls()
      showDialog(friendlyURLs)
    }
  }
}

Here’s what you just did:

  1. Set the initial trackers count.
  2. Added a listener to show a dialog with the blocked events when the counter is tapped.

Then add a call to this function at the end of setupGeckoView():

setupTrackersCounter()

One last thing before this is finished. The count needs to reset when a new URL is opened. Add the clearTrackersCount() to MainActivity.

private fun clearTrackersCount() {
  trackersBlockedList = emptyList()
  trackersCount.text = "0"
}

This clears all the tracker’s information.

Finally, add a call to clearTrackersCount() at the beginning of the onCommit() function.

clearTrackersCount()

Here, you re-set the trackers count when a new URL is entered so you won’t mix trackers from different sessions.

Finally, build run the app.

After the changes, AwesomeBrowser is complete. Try going to different websites to see the counter number increment and reset. Tap on the number to see the full list.

Finished AwesomeBrowser

If you have any issues you can always take a look at the final project. You can find the final project by using the Download Materials button at the top or bottom of the tutorial.