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
This does not scale! Firefox for Android ? Firefox Rocket Firefox Lite Focus for Android ? Firefox for Fire TV ? 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 feature-readerview browser-awesomebar 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<TabSessionState> = 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<S> ): Subscription<S, A> { // .. }
lib-state Observing state val subscription = store.observeManually { state -> // .. } subscription.unsubscribe()
Extension functions Iterating on API @MainThread fun <..> Store<S, A>.observe( owner: LifecycleOwner, observer: Observer<S> ) { // .. }
Extension functions Iterating on API @MainThread fun <..> Store<S, A>.observe( view: View, observer: Observer<S> ) { // .. }
lib-state Dispatching actions Store State observes Action 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> = (S) -> Unit typealias Reducer<S, A> = (S, A) -> S
Channels & Flow
Channel Observing state sequentially @ExperimentalCoroutinesApi @MainThread fun <..> Store<S, A>.channel( owner: LifecycleOwner = ProcessLifecycleOwner.get() ): ReceiveChannel<S> { // .. }
Flow Observing state @ExperimentalCoroutinesApi @MainThread fun <..> Store<S, A>.flow( owner: LifecycleOwner? = null ): Flow<S> { // .. }
“Scoped flow” Observing state @ExperimentalCoroutinesApi @MainThread fun <..> Store<S, A>.flowScoped( owner: LifecycleOwner? = null, block: suspend (Flow<S>) -> Unit ): CoroutineScope { // .. }
Operators for dealing with state updates
Flow operators Only update UI if the state has changed fun <T> Flow<T>.ifChanged(): Flow<T> store . flow () . map { state -> state. selectedTab .title } . ifChanged () . collect { title -> // .. }
Flow operators Only update UI if state changed partially fun <T, R> Flow<T>.ifChanged( transform: (T) -> R ): Flow<T> store . flow () . ifChanged { state -> state. selectedTab .title } . collect { state -> // .. }
Flow operators fun <T, R> Flow<List<T>>.filterChanged( transform: (T) -> R ): Flow<T> 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
Recommend
More recommend