Developer Documentation - krconv/ps-advisor-app GitHub Wiki
PS Advisor Assistant Developer Documentation
This guide walks through the major divisions within the application's codebase (mainly by the directory structure). It describes the responsibilities and goals of each of these sections, and gives a brief explanation of the motivations behind them and where they lay in the typical development workflow.
There are aspects of the architecture and some libraries that aren't covered in the scope of this documentation; however, helpful resources will be linked whenever possible.
data
The data directory controls all of the persistent data that is stored within the application. It uses a database that runs in the background to keep track of information even after restarting. All data within the application (surveys, families, snapshots, users, organizations, etc) originates from here.
But information isn't only stored locally; information also exists on a remote server, which is periodically synced with the local database (see jobs/sync). This remote component of storage is a difficult and complex one, so the data directory is split into functionality for managing these different sources of information.
model
The whole data directory boils down to this set of classes. These model classes are the objects that will be passed around throughout the application and used to populate the UI. There isn’t much more to it. Currently, the model consists of the following data-types:
Survey: The collection of questions that can be presented to a family to answer.
SurveyQuestion (BackgroundQuestion, IndicatorQuestion): The actual questions that a family answers.
IndicatorOption: The options (i.e. red, yellow or green) that are available in an IndicatorQuestion.
LifeMapPriority: The indicators that a family has marked as a priority.
Snapshot: A “snapshot” of all of the answers and priorities that a family creates from a survey.
Family, FamilyMember: The family that is being surveyed.
Location, Country, City: The data describing where a family is located.
User, Login: The information about the user who is currently logged into the application.
These classes will see the most changes in the data directory (which is still not often) because it is the building blocks of the application.
Also noteworthy, each of the model classes have annotations on their attributes (e.g. @ColumnInfo, @PrimaryKey, @Entity) which are used by the local database to figure out how to store the object. More information about the local database can be found in the next section, local.
local
This directory contains the tools needed to talk to the local database, which it does using an Android component Room. In order to use Room, this directory defines:
- DAOs (data-access objects): These interfaces (one for each database) just provide Room with a link between what needs to be stored in the database, and what operations should be available. Behind the scenes, Room uses these files to generate fully-fledged SQL database client implementations that operate on the database. Thankfully, we don't really care about that stuff.
- Converters: With some help of annotations, Room is able to figure out how to serialize/deserialize most data. However, some data simply can't be converted to database rows, and that is why
@TypeConverters exist. These translate the complicated maps needed in the applications (for example, all of the questions on a survey) into a format that Room can store in the database (most often a JSON format).
Room does most of the heavy lifting when it comes to managing the database, so there isn't much to this part of the code-base. The only reason the local area would require changes is to support different operations on the database, or to support new types of data (for example, if Users needed to be stored in the local database).
remote
The second method for storing/retrieving data is from a server. The classes in this directory reflect the data format of the server, along with providing utilities for retrieving data from the server and converting the remote format to and from the local format. Comparable to the functionality of Room, Retrofit is the library used to capture data from the remote database (through its REST API). However, a This directory consists of:
Services: Services are the Retrofit equivalent to Room’s DAOs; they tell Retrofit how to query the remote server to get information. From there, Retrofit generates the actual code to query and parse the data.
Interceptors: Interceptors act as the middle-person between Retrofit and the remote server. They inject things that Retrofit can’t do out of the box, such as authentication.
ConnectivityWatcher: A utility to monitor the connection to the server.
AuthenticationManager: A utility which keeps track of whether the user is authenticated, and configures the AuthenticationInterceptor.
ServerManager: A utility for selecting which remote server to use (in practice, there are a few different servers that users may need to login to), and configures the ServerInterceptor
Within this directory is also the intermediaterepresentation (IR) section, which is what mirrors the format of the remote server. These classes are comparable to the model classes, but are different in that these only exist to be able to retrieve information from the server (no other areas of the application ever use IR). There is a one-to-one mapping of all IR classes to model classes, which the IrMapper performs.
The remote directory will usually need to be modified if the server changes the data format, or if the local model classes change.
repositories
The last component of the data section are the repositories. Trying to manage two data sources isn’t trivial. Consider trying to save a family’s snapshot results; where does that new snapshot get saved? And what if the application is offline? The repositories exist to manage where information is stored, and to synchronize the local database with the remote server. They are the gatekeepers to the actual source of the data; any part of the application that needs information uses a repository, which will make the corresponding calls needed to retrieve the data.
An important note about data throughout the application is that it does not automatically save. That is, just changing the fields of model objects does not change them in the database. In order to make sure information is saved, the save methods in the repositories must be called.
The primary reason for the repositories needing to be updated would be a change to how the local and remote databases are synchronized, or new data types that need to be saved.
injection
Being able to collect all of the tools that exist within the project in a way that their objects can be used throughout the application is a difficult problem. To solve it, we use dependency injection, where classes are given all dependencies that they need when they are instantiated. Dagger allows us to define the utilities that need to be shared throughout the application (e.g. database connection, connectivity information) and it does all of the plumbing needed.
The injection folder consists of application pieces (injection needed to help android figure out how to create our application UI) and persistent information (database access, etc.). We don’t use the more advanced features available with Dagger, using the @Singleton annotation most of the time.
Dagger makes it easy to make new utilities available to the rest of the application, which is the most likely reason for changing anything in the injection directory.
jobs
One of the complexities with creating fully-featured Android applications is the limited and changing hardware constraints, of concern being battery life, processor speed and memory usage. Being so, the Android API provides frameworks which take into account these considerations, which seem to change frequently as the hardware does. To avoid having to make these considerations, we use a third-party tool Android-Job in order to schedule a repeating process to synchronize the local database with the remote server.
While using Android-Job has helped us avoid the complexities of job creation, it limits our ability to keep full control over how jobs are scheduled. For example, we’ve had reports that the application doesn’t synchronize automatically for up to an hour on some platforms, where it supposed to synchronize immediately after connecting to the Internet.. In the end, we believe that this is a great tool for simple jobs, but we might need to look into other tools for scheduling the highly-critical SyncJob.
ui
under construction
- Activities
- Fragment
util
The util directory is for all of the other miscellaneous utilities which don’t fit into our existing divisions.
External Resources
We use many tools to power the application, none of which can be exhaustively described in this documentation. Below is a list of these tools, with some context on how we use them and tips that will be helpful when developing with them.
LiveData- Often originate from the database, to allow data to be updated instantly elsewhere. When a
LiveDataobject is returned from the database, it will be automatically updated anytime a change is made to the database (for example, during a synchronization). - Gotchas:
- In order for the UI to properly handle changes for more complicated containers (i.e. lists), special logic has to be added (i.e.
ListAdapters) LiveDatafrom the database isn’t instant; the underlying data will benulluntil the database fulfills the request in order to avoid blocking calls to the database- Be sure to register an
Observerfor theonChangedcallback
- In order for the UI to properly handle changes for more complicated containers (i.e. lists), special logic has to be added (i.e.
- Often originate from the database, to allow data to be updated instantly elsewhere. When a
- Instabug
- A tool for tracking reported bugs and crashes within the application, and providing a direct line of feedback from users
- mixpanel
- In-depth analytics about the users of the application, particularly good at discovering usability issues within the application.
- Codacity
- Automated code quality checks that are performed whenever a new pull request is submitted
- Gotchas:
- We’ve decided that some of the checks that it performs are not useful, and have disabled them
- CircleCI
- Our continuous integration platform which creates builds of the application and runs our tests against it any time a new pull request is submitted
- timber
- A logging utility that helps us debug issues that caused bugs in production
- Room Persistence Library
- The interface that we use to manage and query the local SQL database
- Gotchas:
- Versioning:
- A
.schemafile is generated which fully describes the structure of the database for a given version of the software. The database version should always be updated before changes are made to it. An indicator that the database is not correct is one of those.schemafiles being changed (which is why they are version controlled). - A
Migrationneeds to be created any time the database is updated, in order for applications running the older version to migrate* to the new version. Forgetting this will cause newly updated applications to continuously crash.
- A
- Versioning: