Skip to content

Latest commit

 

History

History
97 lines (73 loc) · 12.2 KB

DeveloperGuide.adoc

File metadata and controls

97 lines (73 loc) · 12.2 KB

Developer Guide

1. Developer Set-up

  1. Ensure you have Android Studio installed along with Android SDK 28.

  2. Go to https://github.com/JasonChong96/Mugger and click ‘Clone or Download’ to download and later extract the ZIP file.

  3. Open Android Studio and go to File > Open.

  4. Navigate to where the ZIP file was extracted to and open the Mugger project which will have a different icon from regular folders.

  5. Android Studio will automatically download all dependencies needed for the Application [more in section 7].

  6. All java files should be under the package name com.bojio.mugger and resource files under the res folder.

  7. After Android Studio has finished loading the project, press Ctrl + F9 to build an Android Package Kit (APK) for the project.

2. Implementation

This section describes some short noteworthy details of how certain features are implemented. Only a few features have been chosen due to space constraints.

2.1 Storage and Display of Study Session Listings

Listings are stored as individual documents under the listings collection in the Firestore database. Listings are displayed using a RecyclerView that is updated in real time whenever any listings are modified/added/deleted.

2.1.1 Sorting and Filtering of Listings

Chosen implementation: Sorting is handled by the Firestore database

Advantages

Disadvantages

Fetching of sorted data is fast as Firestore automatically indexes the data when it is written to the database.

Firestore cannot filter by a field that is different from the field used for sorting.

Firestore can only sort by one field or 2 predefined fields.

Alternative Implementation: Sorting handled by the client after downloading all Listing data from database

Advantages

Disadvantages

Can sort and filter by multiple fields in any way.

Speed depends on the device

Requires the application to download unnecessary data that will be filtered out

Detrimental to app performance

Rejected as the needed sorting and filtering can currently be done using Firestore queries and manipulation of different fields, e.g to filter by module code and sort by time, add a field that is named after the module code and store the listing time inside this field. Sorting by the module code then achieves the desired filtering and ordering.

2.2 Profile Data and Module Caching

Module Cache data structure

Profile data is cached in a MuggerUser instance. This instance can be fetched using MuggerUser.getInstance(). The data is stored as a Map of String keys and Object values. It is stored this way as these are the types that Firestore returns when fetching user data, and conversion of each individual value into the appropriate type will be done as they are fetched. Trying to typecast values when caching will require one Map object for each data type which has almost no significant advantages. Data Structure of Module Cache Modules are cached in the same MuggerUser instance as a TreeMap of String keys and nested TreeMaps of values. This data structure is shown in the figure above. TreeMaps were chosen as the semesters and module codes should be sorted alphabetically in the UI so that the user can easily choose his/her desired module. This structure allows us to easily fetch modules in a given semester as well as the user’s role in the module efficiently in O(lg(n)) time.

2.3 View Profile

Profile is loaded with the ProfileViewModel class which ProfileFragment instances are linked to. Each text view in profile fragment is linked to a LiveData reference in ProfileViewModel. The ViewModel listens to changes to the user’s profile data on the Firestore database and posts a new value to the LiveData reference when it changes. The active ProfileFragment will be observing such these LiveData objects for changes and the respective TextView will be have it’s display text changed when a new value is posted to the LiveData object. The ViewModel will check if the given field of the profile has been changed from the original value before posting the new value into the LiveData objects as changing UI text is a relatively expensive operation that shouldn’t be called unecessarily. This also applies to the modules taken display in the profile screen, if a new semester or module is added, the UI will be automatically adjusted accordingly.

2.4 The Mute Function

When trying to create a listing or send a new message in listing chats, a check is done against the client cache to check if the user has been muted. And if he/she has been, checks if the current time is past the mute end time. Muted users are disallowed from creating listings and sending messages. When an admin mutes a user, a notification is sent to their client which will show the user that he/she has been muted for the given duration. In addition, the client will automatically update the cache with the end time of the mute. This end time will also be added to the database so simply restarting the client will not reset the mute status. The same process is carried out for unmuting the user. These ensure that mute/unmute operations will be applied in real time.

2.5 Push Notifications

A node.js function is deployed on Firebase Functions that listens to the notifications collection of our Firestore database (This function can be found under the server folder in the main directory). Whenever a new document is added to the collection, the function sends the notification to the relevant users created from the document data. The notifications are sent through the Firebase Cloud Messaging service. For listing chat/delete notifications, they will be sent to users who are subscribed to the channel that is identified by the listing’s unique id (which is automatically generated by firebase when the listing is made). When users join/unjoin a given study session, they will be automatically subscribed/unsubscribed to this channel. Similarly, users will be automatically subscribed to channels for modules that they are currently taking, these channels are identified by the module codes. Notifications will be sent to the module code channel whenever a listing is created. For users who have disabled certain categories of notifications, the filtering is done on the client side and the client will not show the user notifications which they have not enabled. For personal notifications such as mute/unmute notifications, the notification is sent to the user’s device only through the device’s instance id which is stored in the Firestore database on login.

2.6 Custom Filters

As mentioned in section 4.1.1, the listing data fetched from Firestore cannot be filtered/sorted by different fields on their end, hence the filtering has to be done on the client side in an efficient manner that will not cause a significant decrease in performance. The data fetched from the Firestore database depends on the category they choose, either “All listings”, “Listings I have joined” and “My Listings”, all these correspond to an existing query already used for other features. As for the other filter settings, each correspond to a Predicate of their own which are composed into a single predicate using the composePredicate method (The predicate#and method introduced in Java 8 cannot be used here as it is not supported by older versions of Android). When a listing is loaded, it is tested using the generated predicate and if it does not pass, the view for the listing is not processed and is hidden from the user. To keep the functionality of getItemCount in the FirestoreRecyclerAdapter class, these filtered listings are added in a HashSet and removed when they no longer exist in the database. These operations are done in O(1) time and a HashSet disallows duplicates by design, making it efficient. The item count can then be calculated by the number of items fetched by the query minus the number of items in the hashset. This getItemCount method is important in showing the user a message instead of an empty screen when there are no listings that match

2.6.1 Custom Filter Settings Storage

The custom filter settings have many groups of options and on-off filter switches, e.g category, show/hide student listings, show/hide TA listings, show/hide Professor’s listings. Creating a new field in the database for each switch would be inefficient and a waste of memory. Hence all such switches are stored in a single long object and can be fetched by applying a bitmask to the number, e.g applying bitwise AND with the number 4 will give the number 4 if student listings are to be shown and give 0 if they are to be hidden. String filters, however, cannot be stored this way and still have to be stored in separate fields.

2.7 Presentation of Listing Dates

If the date is within a day of the current date, it’ll be represented as either “Today”, “Tomorrow”, “Yesterday”. If not, it’ll be represented as last (day of the week) or this (day of the week) wherever possible. If neither of those representations are possible, the raw date is shown based on the device’s date format. Next (day of the week) is not used as it can lead to some ambiguity, e.g if today is monday and we say next sunday, the user would not be 100% sure if it means the coming sunday 6 days later or the following sunday 13 days later. Although it might be obvious to some of us that it is the former, this slight ambiguity should still be avoided to prevent confusion.

2.8 My Schedule

The app iterates through all listings that the user is currently joining and marks the dates that are involved in them. When a date is clicked, a predicate is created to check if a listing starts before the date and ends after the start of the date or if a listing starts during the date. This will predicate will filter out listings that are unrelated to the date chosen in a similar manner to the custom filter implementation in 10.1.1.

3. Dev Ops

3.1 Build Automation

By default, Android Studio uses Gradle for build automation. Gradle automatically downloads and imports relevant dependencies listed under build.gradle, along with the required Android SDK version and build tools.

3.2 Continuous Integration

The github repository is linked to Travis CI. Unit tests will be automatically carried out by Travis CI and the result will be shown on the README. If the build or tests fail, there will be an icon to indicate as such on the top of the README. The build on Travis CI’s side will be automatically signed with the same production signature being used on our Play Store builds. Integration tests are not carried out by Travis CI at the moment due to issues with Firebase not being available on the Travis CI android emulators. Due to time constraints, we have decided to prioritize bug fixing and improving the frontend of the application before trying to fix this.

4. Architecture

Mugger makes use of the Android Architecture Components and largely follows the Model-View-ViewModel architecture. This creates a separation of logic and UI code with Activity/Fragment classes focusing mainly on the UI and the ViewModel classes focusing on the logic and communicating with the Model and database.

4.1 Model

The model represents the data and logic of the application. For our application, an example of this would be the MuggerUserCache class which stores a cached local copy of the user data.

4.2 ViewModel

The ViewModel interacts with the model and its main role is to contain the logic separately from the UI code. LiveData objects are also used for the views to observe changes in data for data that are volatile, e.g the number of people attending a study session. Refer to section 4.3 for an example of how LiveData is used. The view model also persists through reconfigurations such as screen rotations, unlike activity/fragment classes, this ensures that the data loaded persists throughout reconfigurations. Without the ViewModel, activity/fragment classes have to reload data every time a rotation happens, which is very inefficient. This life cycle is represented by the picture below, showing the lifecycle of an activity on the left and the ViewModel on the right. (Taken from Google documentation) image::https://i.imgur.com/5g2xwcO.png[LifeCycle of Activity vs ViewModel]

4.3 View

The View is simply the UI shown to the user, handled by the Fragment/Activity classes.