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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Android Tutorial for GeckoView: Getting Started
25 mins
What if I told you you could load a web page or any web content without using a WebView
. Is it that even possible? Yes, it is with GeckoView.
GeckoView is an open source library that allows you to render web content on Android using the Gecko web engine. In this tutorial, you’ll learn more about GeckoView and other Android tools used for web content.
What is GeckoView?
GeckoView is an Android library which brings the Gecko web engine to the Android community. It was created by the community and the developers at Mozilla. The folks at Mozilla have a lot of experience with the web and browsers in general.
Have you heard about FireFox? They’re the people behind it. :]
FireFox and GeckoView both use the web engine Gecko. While GeckoView is a new library, it powers many cool open source apps like FireFox Preview, FireFox Focus and FireFox Reality. You can even contriubte to other awesome projects powered by GeckoView.
GeckoView vs WebView
WebView
and GeckoView are not the same. GeckoView has its own API and is not a drop in replacement. Here are some major differences between them:
- While
WebView
was not designed to create web browsers GeckoView was. It exposes the entire power of the Web to Android applications. -
WebView
can be updated, however consistency in API and behavior can vary from device to device. This makes it harder to develop for. In contrast, GeckoView is self-contained. You’ll ship the whole API with your app so you’ll get what you tested with no surprises. - GeckoView complies with web standards. Moreover, it’s backed by Mozilla, an organization with a history of fighting for the open web.
- GeckoView provides access and control over low level web APIs. It also has built-in support for private mode, tracking protection, WebVR and, coming soon, Web Extensions support.
Finally, GeckoView
adds additional space to your final APK. For example, you can see a growth up to 30MB to 40MB from the library alone. This is because GeckoView includes the whole web engine with the app.
If you’re considering GeckoView for your app, ask yourself these questions:
- Are you developing a web browser-ish or similar app?
- Do you need guaranteed access to web APIs?
- Are you developing a game that relies on web technologies?
If you answered yes, GeckoView
could be a great option for you. In contrast, if you only want to render some web content without API warranties WebView
could be your best bet.
Getting Started
While working on the tutorial, you’ll create an app called AwesomeBrowser. It’s a small web browser that showcases the power of GeckoView. It allows normal browsing and includes tracking protection enabled by default.
First, download the AwesomeBrowser project using the Download Materials button at the top or bottom of this tutorial. Then open the project in Android Studio 3.3 or later by selecting Open an existing Android Studio project on the Android Studio welcome screen.
Before continuing with the tutorial, take a look at the project structure.
Exploring the AwesomeBrowser Structure
This is the source code’s structure:
-
MainActivity.kt Here you’ll handle the
geckoView
instance and toolbar. - activity_main.xml Here you’ll define the main layout.
- Helpers.kt This contains a set of helper functions and constants.
As you can see in the above image, there are more files I didn’t mention for the sake of brevity. You won’t need to worry about them for this tutorial, but feel free to poke around if you’re curious.
Build and run the app. All you’ll see is a blank screen until you add the UI!
Setting Up GeckoView
Begin with the starter project. For the moment, your main goal is to configure GeckoView
‘s dependency and then load a page with it.
GeckoView is distributed as a Gradle dependency and its artifacts live in maven.mozilla.org. To include it, in the project level build.gradle add the maven repository to the allprojects
block right under jcenter()
.
build.gradle
maven {
url "https://maven.mozilla.org/maven2/"
}
This includes all the artifacts in the maven Mozilla repository.
Next, add the following code to the app ‣ build.gradle, after the apply: plugin
lines, and before the android
block:
ext {
geckoview_channel = "nightly"
geckoview_version = "70.0.20190712095934"
}
Similar to FireFox, GeckoView has different channels for different purposes. These include the latest stable release, the beta tested release and the nightly developer build. In this case, you’ve chosen the nightly build because you’ll get the most updated features and bug fixes.
To keep things neat and tidy, you’ve added a variable for the version of Gecko to use and the channel from which to retrieve it.
Next, add the following to app ‣ build.gradle below the buildTypes
block, inside the android
block:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
As GeckoView uses some Java 8 APIs, it requires these compatibility flags.
Finally, for the last Gradle configuration, add the following code inside the dependencies
block in the same file.
implementation "org.mozilla.geckoview:geckoview-${geckoview_channel}:${geckoview_version}"
Above you’re adding the dependency for GeckoView dependency to the project.
Sync your project with the gradle files now that you’ve made these changes.
Adding Some UI
To interact with GeckoView
you need two main objects: a GeckoView
widget and a GeckoSesssion
.
GeckoView Widget
This is the UI component you’re going to use for rendering web content. It’s a normal Android view you can create using either XML or code.
GeckoSession
A session represents a tab in a web browser. It contains the URL you want to load and all of its additional configurations.
Imagine that you have a web browser with multiple tabs on it.
Each tab, or GeckoSession, has:
- State linked to it.
- Properties like
isPrivateMode
enabled or disabled. -
isDesktopMode
enable or disabled. - Its own browsing history.
You want to keep all this information separated by tab and decoupled from the UI, GeckoView
widget.
This is a job for GeckoSession
. It’s an object that holds all these configurations in one place making it easy and straightforward to work with it.
This powerful concept decouples the view, GeckoView
, from the state, GeckoSession
. This way you could have one view, GeckoView
, in charge of rendering and multiple sessions, GeckoSession
, representing the state of each tab.
This would be tricky to achieve using WebView
. Because of the lack of sessions, it applies configurations directly to the view. :[
GeckoSessionSettings
A great way to customize how a session should behave is by providing a GeckoSessionSettings
object. You can do that by passing the settings object to the GeckoSession
constructor.
You create a settings object using its builder GeckoSessionSettings.Builder(). Here’s an example of how you might do this:
val settings = GeckoSessionSettings.Builder()
.usePrivateMode(true)
.useTrackingProtection(true)
.userAgentMode(USER_AGENT_MODE_MOBILE)
.userAgentOverride("")
.suspendMediaWhenInactive(true)
.allowJavascript(true)
These are some other things you can configure on a session:
- usePrivateMode: Indicates if the session should be private.
- useTrackingProtection: Indicates if tracking protection should be enabled.
-
userAgentMode: Indicates the page layout. You can use these options:
USER_AGENT_MODE_DESKTOP: Enable desktop mode.
USER_AGENT_MODE_MOBILE: Disable desktop mode.
USER_AGENT_MODE_VR: Layouts for a VR experience. - userAgentOverride: Changes the user agent identifier.
- suspendMediaWhenInactive: Indicates whether the media should continue playing when the app is suspended.
- allowJavascript: Indicates if JavaScript should be enabled.
Notice that every GeckoSession
holds its own settings object. This allows you to do much customization without creating multiple instances of GeckoView
. Remember the multiple tabs example.
Now, that you understand how GeckoView
widget and GeckoSession
work, you can add them to AwesomeBrowser.
First, open activity_main.xml to add the GeckoView
widget. Replace the TextView
with the GeckoView
widget below.
<org.mozilla.geckoview.GeckoView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/geckoview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"/>
Next, wire it in the MainActivity.kt file.
Add two private properties to MainActivity
: one called geckoView
, the UI widget, and another called geckoSession
, your tab:
private lateinit var geckoView: GeckoView
private val geckoSession = GeckoSession()
Here, you declare two properties so you can access them from different functions.
Next, create a function called setupGeckoView()
. Remember to import all the new references.
private fun setupGeckoView() {
// 1
geckoView = findViewById(R.id.geckoview)
// 2
val runtime = GeckoRuntime.create(this)
geckoSession.open(runtime)
// 3
geckoView.setSession(geckoSession)
// 4
geckoSession.loadUri("about:buildconfig")
}
Here’s what your code above does:
- First you bind the
GeckoView
object from the XML. - You then create a
GeckoRuntime
which is responsible for holding all the state for the underlying Gecko engine. - To be able to load an URL you need a
GeckoSession
. Here you assign it to theGeckoView
instance. This way theGeckoView
will know which info needs to load. - Finally, you use the
GeckoSession
instance to load a URL. In this case,about:buildconfig
, an internal page that shows all the configurations of this GeckoView build. This can be whatever url you like.
As you know, the GeckoView
widget has its own API. This code would look very different if you were using a WebView
.
To make use of your new method, add a call to setupGeckoView()
to the bottom of onCreate()
:
setupGeckoView()
You’re almost ready to run the app. But first you need to setup a proper build variant according to the device or emulator architecture that you’re going to test on. Open the Build Variants window in Android Studio and select your architecture:
You can see your emulator CPU architecture by going to Tools ▸ AVD Manager. You’ll see it on the grid cell under CPU/ABI.
Most devices out there are based on ARM CPU architecture, but you have to verify yours.
Now, build and run the app. :]
If you have everything set up correctly, then you’ll see this:
Above you loaded an internal page called about:buildconfig
. It’s a page that shows all the configurations of this GeckoView build, but you can replace it with whatever url
you like.

You can fix this by first selecting the correct build variant and then running the app again.
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:
- Set up the toolbar when the activity starts.
- 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.
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.
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
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:
- Set the initial trackers count.
- 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.
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.
Where to Go From Here?
GeckoView is a huge and amazing project and while this tutorial couldn’t cover everything it has to offer, hopefully it awakened your curiosity.
To get a better idea of everything GeckoView has to offer, take a look at the official website geckoview.dev, also you can find the GeckoView source code here. Additionally, the GeckoView team has a sample app where they show all its features.
Here’s a special shout-out to Emily Toop, a member of the GeckoView team, as well as Dan Callahan, developer advocate at Mozilla, for all their help during this tutorial. If you have any questions about the APIs or the project, they’ll be more than happy to help.
I hope you enjoyed this tutorial as much as I did. If you have any comments or questions, please join in on the forum discussion below!