Structure of Code

User Interface

The screens displayed to users are called activities. Each one has an associated *Activity.java file that defines a *Activity class. For example, the timeline for a network has an associated TimelineActivity that controls it. Each activity may also include fragments (e.g. ListNetworksFragment) that define part of an activity and can be reused across multiple activities. They are also often used for parts of the activity that sometimes disappear or are exchanged with other fragments. Each activity and fragment may also have layouts defined in the res folder as activity_*.xml and content_*.xml.

Adapters

In some activities, large scrollable lists need to be displayed. Generating the displayable list elements (View s) for all the items is inefficient, so RecyclerView s are used, which efficiently handle generating the list using adapters. These classes (e.g. RVAdapter) provide the functionality RecyclerView needs to dynamically generate each displayed list element.

Data Models

Conceptually, the data stored with CultureMesh, including Place s, User s, Network s, and Event s are represented as models. These models are represented in JSON when in transit between the app and the server and as instances of the appropriate class (see models) within the app. The API class handles converting between object and JSON formats, often using constructors and getters within the model’s class (e.g. Post.getPostJSON).

Places and Locations

Place s and/or Location s are part of the definition of a Network, and they are used by themselves when displaying lists from which the user can choose parameters to narrow their search for networks.

The difference between a place and a location is not well captured in their names. A place defines a particular geographic area that is one of three types: a City, a Region, or a Country. A location can be any of those three, and includes definitions for one or more places. For example, a location might refer to San Francisco, in which case it would store San Francisco, California, United States.

Places can also store references to parent places, so this distinction may seem unhelpful. However, we use it because the salient difference between a location and a place is that a place is a separate model that stores all the information CultureMesh has on that place (e.g. name, population, etc.). On the other hand, a location only stores the IDs of the places that define it. In practice, this means that places can be thought of as residing in a list of all places CultureMesh knows about, while locations are used to define networks.

Inheritance Structure

                   Location
                  (IDs only)
                  /        \
                 /          \
                /            \
               /              \
      Place (Abstract)    DatabaseLocation (Abstract)
       (Full Info)                   (IDs)
         /  |  \                   /      \
        /   |   \           NearLocation  FromLocation
   City  Region  Country  (Wrappers for DatabaseLocation)
(Specific cases of Place)

The diagram above illustrates the inheritance hierarchy consisting of classes storing location/place information. The tree rooted at DatabaseLocation exists because of the potential to cache data locally in a database. This would allow for offline access and better performance when internet connection is poor. However, the database we experimented with required that the near (or current) location be specified using a different class than the from (or origin) location so that their instance fields could have different names and not conflict in the database. This is why NearLocation and FromLocation exist, as they are otherwise essentially the same. Whenever they can be treated identically, DatabaseLocation can be used. DatabaseLocation also stores functionality that is common to both subclasses.

Networks, Languages, Events, and Posts

A Network is defined in one of two ways:

When the network is initially received from the server as a JSON, it is parsed to create a DatabaseNetwork, which represents the above properties by their IDs. Then, that DatabaseNetwork is expanded into a Network, which includes full Place and/or Language objects for the above properties.

While not stored in the Network object, there are also lists of Event s and Post s associated with each network. These are fetched separately from the server each time they are needed. Instead of separate classes for their ID-only representations coming from the server and the fuller ones used within the app, they are instantiated in stages within the API class. First, their JSON representations are parsed to partially instantiate them. Then, missing parts (e.g. full Network objects) are fetched from the server and parsed to fully instantiate the objects.

Both Event and Post are subclasses of FeedItem, which requires them to have a public instance field containing a list of comments. This allows them to both be displayed via polymorphism within a feed like TimelineActivity. These comments are represented by PostReply objects.

Interfaces for Sending Objects

To reduce code redundancy, the API class uses a series of model methods that can send PUT and POST requests (separate model methods) with any object so long as that object can generate a JSON representation of itself for the request using getPutJSON or getPostJSON. The presence of these methods is enforced by the interfaces Postable and Putable, which allows for the model methods to be polymorphic.

Other

A Point describes a particular spot on the globe in terms of its latitude and longitude. It is really just a holder for the two values.

A User object represents any of CultureMesh’s users. It only stores parts of their public profiles, so methods that work with private information like passwords or email addresses take those values as parameters.

Connections to CultureMesh’s Servers

Networking operations are performed by making calls to methods in the API class. Since networking operations suffer from any inherent latency in the user’s internet connection, they are performed in a separate thread using Volley. Generically then, these methods generally take the following arguments: (RequestQueue, args ... , Response.Listener<responseType>)

  • RequestQueue: A queue that holds the asynchronous tasks to execute. A queue is generally created once for each activity and then used for all API calls in that activity.
  • args: All the arguments the method needs to create the network request. This often includes IDs of resources to fetch.
  • Response.Listener<...>: A listener whose onResponse method is called with the result of the operation. This occurs whether or not the operation completed successfully.
  • responseType: The type of the object that is returned by the operation. This is generally some kind of NetworkResponse object.

API Authentication

API Key

The API key must be passed as a parameter with key key in the URL of all authenticated API endpoints. The key is stored in Credentials, which is not stored in version control or published publicly. The API method API.getCredentials method is used to access the key from within the API class.

User Credentials

When the user logs in to the app the first time, their email and password are used to authenticate a request for a login token using API.Get.loginWithCred. This token is stored in the app’s SharedPreferences for future authentication. The user’s password is not stored. If the token expires due to inactivity, the user is directed to login again.

All tokens older than API.TOKEN_REFRESH milliseconds are refreshed with the next authenticated request (this is handled automatically by API.Get.loginToken, which produces the tokens used by all API methods that access secured endpoints). Tokens are refreshed much faster than they expire because the difference between the refresh time and the expiration time is the maximum allowable inactivity period before users have to sign in again, and we want this to be long enough to avoid too much inconvenience.

Conveying Network Responses

This object simplifies error reporting by storing whether or not the operation failed using NetworkResponse.fail. It also stores the results of successful operations, which are available through NetworkResponse.getPayload. It can store messages describing errors and create ready-to-display error dialogs to communicate those messages to users using NetworkResponse.showErrorDialog.

Authentication Failures

In the special case of authentication errors, the NetworkResponse.setAuthFailed method can be used to specify that the failure was authentication-related. When the resulting error dialog is displayed and dismissed, the user is automatically redirected to the sign-in screen.

Other

First Activity

When the application starts from scratch (i.e. is not being launched by restoring a previous state), the ApplicationStart activity is loaded. This performs initialization for the app (e.g. Crashlytics) and redirects the user to either TimelineActivity, OnboardActivity, or ExploreBubblesOpenGLActivity based on whether they have logged in and whether they have a selected network.

Managing Formatted Text

In cases where the user can create formatted text using inline markup (i.e. bold, italics, and hyperlinks), FormatManager handles the markup.

Handling Redirections

In a few cases, a parent activity needs to launch a child activity while also directing the child to launch a particular grand-child activity. For example, when SettingsActivity launches OnboardActivity, the user should be sent back to SettingsActivity at the end. If ApplicationStart is instead launching OnboardActivity, the user should next be sent on to LoginActivity. This is handled by Redirection.