BUILD A BROWSER BY
KOTLIN COLIN LEE SEBASTIAN KASPARI
@colinmlee @Anti_Hype Copenhagen Denmark Feature
Session Toolbar Downloads Contextmenu Customtabs Findinpage Webnotifications
Reader View Search Sync Prompts Sendtab QR ...
Browser
Toolbar Engine-System Engine-Gecko Search Tabstray Menu ...
Concept
Engine Toolbar Storage Fetch Push Tabstray ...
UI Service Support Lib
Autocomplete ...... State Crash ... Agenda
● Why?
● How?
● Live code a browser!
● Syntactic Sugar Why? Firefox for Android Firefox for Android
Focus for Android Firefox for Android
Firefox Rocket Firefox Lite
Focus for Android Firefox for Android
Firefox Rocket Firefox Lite
Focus for Android
Firefox for Fire TV
Firefox for Echo Show Firefox for Android ?
Firefox Rocket Firefox Lite
Focus for Android
? Firefox for Fire TV ?
Firefox for Echo Show
Firefox for Android ?
Firefox Rocket Firefox Lite
Focus for Android
? Firefox for Fire TV This does not scale! ?
Firefox for Echo Show Goals
● Share code between our apps
● Reduce the maintenance overhead
● Clear architecture boundaries
● Make it easier and faster to build new or experimental products. Solution:
Build independent, reusable components to share code between existing projects and build new apps faster. Components?
● Independent open-source Android libraries
● “Android First” extensible application architecture
● Minimal cross-component and third-party dependencies
● Customizable and pluggable
● Distributed as AARs via a Maven repository Components?
100% Kotlin Components?
100% Kotlin Well, and some Rust How? Browser Engine HTML
CSS Browser Engine
JS Browser Engine
WebView GeckoView Servo ... GeckoView GeckoView
● App developer in control of updates. ○ Only one version needs to be supported. No surprises.
● Multiprocess
● Separation of session (tab) and view
● Private mode
● WebExtensions
● Tracking Protection Engine Components
browser-engine-system
concept-engine browser-engine-gecko
... Toolbar
Engine Components
browser-toolbar concept-toolbar ...
Done? What else?
feature-media
feature-downloads browser-contextmenu
browser-awesomebar feature-readerview
feature-downloads Feature
Session Toolbar Downloads Contextmenu Customtabs Findinpage Webnotifications
Reader View Search Sync Prompts Sendtab QR ...
Browser
Toolbar Engine-System Engine-Gecko Search Tabstray Menu ...
Concept
Engine Toolbar Storage Fetch Push Tabstray ...
UI Service Support Lib
Autocomplete ...... State Crash ... LIVE CODING Syntactic Sugar
Mozilla ❤ Kotlin null, null?, null!! Explicit nullability Handling state lib-state Store & State
Store
State data classes Modeling State
data class BrowserState( val tabs: List
Store
State
observes
State
Component / App lib-state Observing state
@CheckResult(suggest = "observe") @Synchronized fun observeManually( observer: Observer ): Subscription { // .. } lib-state Observing state
val subscription = store.observeManually { state -> // .. }
subscription.unsubscribe() Extension functions Iterating on API
@MainThread fun <..> Store.observe( owner: LifecycleOwner, observer: Observer ) { // .. } Extension functions Iterating on API
@MainThread fun <..> Store.observe( view: View, observer: Observer ) { // .. } lib-state Dispatching actions
Store
State
Action observes
State dispatches Component / App Sealed classes Modeling Actions
sealed class TabListAction : BrowserAction() { data class AddTabAction( val tab: TabSessionState, val select: Boolean = false ) : TabListAction()
data class SelectTabAction( val tabId: String ) : TabListAction()
// .. } lib-state Inside the store
Store
Action Reducer State
State Functions & Sealed classes Reducing state
fun reduce( state: BrowserState, action: TabListAction ): BrowserState { return when (action) { is TabListAction.AddTabAction -> { .. } is TabListAction.SelectTabAction -> { .. } // .. } } data classes Creating a new state
val newState = state.copy( selectedTabId = "some-other-tab" ) Type aliases Observing state
typealias Observer = (S) -> Unit
typealias Reducer = (S, A) -> S Channels & Flow Channel Observing state sequentially
@ExperimentalCoroutinesApi @MainThread fun <..> Store.channel( owner: LifecycleOwner = ProcessLifecycleOwner.get() ): ReceiveChannel { // .. } Flow Observing state
@ExperimentalCoroutinesApi @MainThread fun <..> Store.flow( owner: LifecycleOwner? = null ): Flow { // .. } “Scoped flow” Observing state
@ExperimentalCoroutinesApi @MainThread fun <..> Store.flowScoped( owner: LifecycleOwner? = null, block: suspend (Flow) -> Unit ): CoroutineScope { // .. } Operators for dealing with state updates Flow operators Only update UI if the state has changed
fun
store .flow() .map { state -> state.selectedTab.title } .ifChanged() .collect { title -> // .. } Flow operators Only update UI if state changed partially
fun
store .flow() .ifChanged { state -> state.selectedTab.title } .collect { state -> // .. } Flow operators
fun >.filterChanged( transform: (T) -> R ): Flow
store .flow() .map { state -> state.tabs } .filterChanged { tab -> tab.title } .collect { tab -> // .. } https://mozac.org https://mozilla.github.io/geckoview https://mozilla.github.io/application-services/ https://github.com/mozilla-mobile/ THANK YOU AND
REMEMBER
TO VOTE
Colin Lee @colinmlee Sebastian Kaspari @Anti_Hype #KotlinConf