<<

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- 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? 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? HTML

CSS Browser Engine

JS Browser Engine

WebView GeckoView ... GeckoView GeckoView

● App developer in control of updates. ○ Only one version needs to be supported. No surprises.

● Multiprocess

● Separation of session () 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 = emptyList(), val selectedTabId: String? = null, val customTabs: List<..> = emptyList(), val extensions: Map<..> = emptyMap() ) : State lib-state Observing state

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 & 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 Flow.ifChanged(): Flow

store .flow() .map { state -> state.selectedTab.title } .ifChanged() .collect { title -> // .. } Flow operators Only update UI if state changed partially

fun Flow.ifChanged( transform: (T) -> R ): Flow

store .flow() .ifChanged { state -> state.selectedTab.title } .collect { state -> // .. } Flow operators

fun Flow>.filterChanged( transform: (T) -> R ): Flow

store .flow() .map { state -> state.tabs } .filterChanged { tab -> tab.title } .collect { tab -> // .. } ://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