Skip to content

Latest commit

 

History

History
49 lines (42 loc) · 4.72 KB

File metadata and controls

49 lines (42 loc) · 4.72 KB

State of the union

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.

How we got here

  • 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

Where we are now

  • 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 FormFillingActivity made up of ODKView)
  • 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 (FormsRepository and Form for 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
  • SharedPreferences is wrapped in app's own Settings abstraction
  • 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 the Scheduler abstraction backed by Kotlin coroutines

Where we're going

  • 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 FormFillingActivity into other components (like Fragments, ViewModels, use cases etc)
  • Writing all new code in Kotlin, and writing new UI using Jetpack Compose (utilizing ComposeView within 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 AsyncTask with Flow (converted to LiveData in UI code) and replacing the previous Scheduler abstraction with lifecycle-aware coroutines (such as viewModelScope)
  • Gradually removing use of CursorLoader (all remaining uses are in CursorLoaderFactory)
  • Using AndroidX Test in new local tests and migrating other local tests as we touch them (from classic Robolectric)
  • Improving the MapFragment abstraction so more logic can be shared between the map engines