The purpose of this document is to give anyone who reads it a quick overview of both the current state and the direction of the code. The community should try and update this document as the code evolves.
- App originally built in Java for Android 1.0/T-Mobile G1
- Written at Google by graduate student interns and then University of Washington
- Designed as a survey application backed by JavaRosa (which deals with XForm forms) communicating with OpenRosa servers
- Many different contributors/styles/eras over 15+ year lifetime
- App wasn't built with a TDD workflow or with automated testing
- Lots of work in the last few years to add more tests and clean up code using coverage measurement and static checks
- App written in a mixture of Java and Kotlin
- App has mixture of unit tests, "Local tests" and "Instrumented tests" but coverage is far from complete
- Local tests are a mixture of classic Robolectric and AndroidX Test/Espresso (backed by Robolectric)
- UI is "iconic" (old) but with a lot of inconsistencies and quirks and is best adapted to small screens (although often used on tablets)
- A lot of code lives in between one "god" Activity (
FormFillingActivity) and a process singleton (FormController) - Core form entry flow uses custom side-to-side swipe view (in
FormFillingActivitymade up ofODKView) - Questions are rendered using a view "framework" of implementations inheriting from
QuestionWidget(which is documented in WIDGETS.MD) which are also used to store UI state during form entry - Almost all app data is stored in Android's user-accessible "external" rather than "internal" storage
- App mostly stores state in multiple SQLite databases - this means there's no DB level integrity guarantees on some relationships (like between forms and instances for example)
- Access to state in SQLite happens through repository objects which deal in data/domain objects (
FormsRepositoryandFormfor example) - Settings UIs for the app use Android's Preferences abstraction
- App uses Material 3 Theming so Material components are preferred over custom or platform ones.
- Dagger2 is used to inject "black box" objects such as Activity and just uses a very basic setup
- Http is handled using OkHttp3 and https client abstractions are generally wrapped in Android's AsyncTask
- Geo activities use two engines (Mapbox or Google Maps) depending on the selected basemap
- Code goes through static analysis using CheckStyle, PMD, ktlint and Android Lint
- Forms get into the app from two different sources (Open Rosa servers and disk) but the logic for this is disparate and they don't sit behind a common interface
- Instances are linked to the forms they are instances of through formid and version. However, the same formid and version combination could represent multiple forms in storage
SharedPreferencesis wrapped in app's ownSettingsabstraction- The form hierarchy is rendered using
FormHierarchyFragment, which hasn't been seriously touched (at a code or design) level for a few years - Async work is currently a mix: some parts still use legacy
AsyncTask, while others use theSchedulerabstraction backed by Kotlin coroutines
- General effort to increase test coverage and quality while working on anything and enforcing tests for new code in PR review
- Moving responsibilities out of
FormFillingActivityinto other components (like Fragments, ViewModels, use cases etc) - Writing all new code in Kotlin, and writing new UI using Jetpack Compose (utilizing
ComposeViewwithin existing Activity/Fragment structures) - Moving towards a "data services" oriented architecture that has emerged over time
- Writing new code using a multi-module approach (feature modules, mini frameworks etc) and breaking old code out into modules when opportunities come up
- Trying to remove technical debt flagged with
@Deprecated - Replacing async work such as
AsyncTaskwithFlow(converted toLiveDatain UI code) and replacing the previousSchedulerabstraction with lifecycle-aware coroutines (such asviewModelScope) - Gradually removing use of
CursorLoader(all remaining uses are inCursorLoaderFactory) - Using AndroidX Test in new local tests and migrating other local tests as we touch them (from classic Robolectric)
- Improving the
MapFragmentabstraction so more logic can be shared between the map engines