At Neeva, we believe innovation in product development goes hand in hand with staying on the cutting edge of development practices. With this in mind, we reimagined an architecture for our Android browser from the ground up as a modern Android app. We also believe that product quality and end user experience thrives in an open environment in which best development practices are freely shared amongst a growing community of innovators. That is why we open sourced our iOS browser and now we are taking the next step by open sourcing our Android browser as well.
When it came to building the Android app, even when we abstract away the web rendering engine with a clean boundary, the remaining browser internals and how they interact with the browser’s UI components constitute a pretty elaborate machinery. As we ventured to build these app pieces from scratch, we wanted to choose foundations that will keep us true to a vision of constant innovation with ease. To obtain a modular codebase that is highly testable, we built on JetPack core dependencies that are not commonly seen in any Android browser out in the Play Store today.
First of all, for building the user interface, we chose Jetpack Compose, a modern declarative UI Toolkit for Android that makes it much easier to express and build complex UI, and adopted an all Compose UI hierarchy. Despite its clear benefits, this was still a pretty involved decision as the project entered Stable only a few weeks after we broke ground in our Android repo. Our team was encouraged by the excitement and dedication with which the JetPack team approached the project, and we braved through the initial releases, refactoring our UI hierarchy multiple times in the process to settle on the final design.
Despite the modularity and ease Compose provides, this process was by no means trivial. Adopting Compose at an early stage also means some of the dependencies that are essential like Navigation Animations for Compose and core UI layouts like grid are still in early development and the team had to implement in-house solutions for functionality that would come for free in a View based UI hierarchy. It also meant bearing the brunt of the bugs that came out in the alpha version of these libraries.
We wanted daily app development on the browser to not be any different from any modern Android app which meant using Kotlin, Coroutines, and JetPack libraries across the codebase. All the information relayed across components of the app is expressed via Flows, so that we freely benefit from the concurrency framework provided by coroutines throughout the app. That said, using them definitely posed issues for our testing infrastructure. Setting up the app in a way that allowed us to swap out CoroutineScopes and Dispatchers while our tests are running allowed us to bypass most of the problems, but we do continue to work around issues with events not triggering as expected – even with the testing libraries that are provided.
For dependency injection across services in the app, picking a commonly used library like Hilt (which is a wrapper around Dagger) was our first choice. Compose also enables its own take for Dependency Injection through the use of CompositionLocalProvider. Our team took a very principled approach to building a well balanced set of Services and ViewModels so that making all available through an environment like this doesn’t result in overarching dependencies across different components of the app making each component easier to unit test.
The trickiest challenges around keeping a full screen Compose hierarchy came at the points where constraints around our Chromium integration met with our UI components. For example, each profile for WebLayer has its own Fragment, so switching between normal and incognito modes requires swapping two Fragments and the browser instances that are tied to them. So making the tab switcher toggle (which is a Composable) handle this switch while swapping browser and fragment instances behind the scenes requires tricky coordination. The team had to be extra diligent in initializing internal components and accounting for a lot of corner cases (like the app dying because of OOM while the user is viewing incognito tabs) while building a smooth animation that swaps the grid of normal tabs with the grid of incognito tabs.
The browser has a few services like history and Spaces (Neeva’s unique take on bookmarks) that require us to write our own databases. After debating alternatives, we settled on using Room for both, which provides a lot of benefits like compile time verification of SQL queries and easy migration capabilities combined with pretty convenient integrations with the rest of JetPack libraries. For both History and Spaces, we benefited quite a bit from expressing query outputs as Flows or PagingSource, where there are associated libraries integrating with Compose on the UI end as well.
All in all, the choice of Compose and JetPack Architecture components were both bets we made on bridging Android best development practices with a modern browser design for the first time. We believe that this netted us with a codebase that follows a modular design across all components, is easy to develop and innovate on and is highly testable. We are proud of the progress we have made so far and are looking forward to sharing more details of this adventure with the Android developer community soon.
We love the mobile web and love the opportunity to make it work better for people. We welcome contributors to the project to join us in developing this new open source browser and building a community focused on making the mobile web a better place!