Skip to content

Latest commit

 

History

History
818 lines (681 loc) · 47.9 KB

CONTRIBUTING.md

File metadata and controls

818 lines (681 loc) · 47.9 KB

Contributing

It is recommended that you go through our workshop first, to familiarize yourself with the technologies and the contribution process.

Table of Contents

Pull Request (contribution) process

Guidelines

  1. Check out this tutorial if you don't know how to make a PR.
  2. Increase the version number in the pubspec.yaml file with the following guidelines in mind:
    • Build number (0.2.1+4) is for very small changes and bug fixes (usually not visible to the end user). It should always be incremented, never reset, as it is what Google Play uses to tell updates apart.
    • Patch version (0.2.1+4) is for minor improvements that may be visible to an attentive end user. It should reset to 0 when the minor/major version increments.
    • Minor version (0.2.1+4) is for added functionality (i.e. merging a branch that introduces a new feature). It should reset to 0 when the major version increments.
    • Major version (0.2.1+4) marks important project milestones.
  3. Document any non-obvious parts of the code and make sure the commit description is clear on why the change is necessary.
  4. If it's a new feature, write at least one test for it.

Please note that in order for a PR to be merged (accepted), all of the tests need to pass, including the linter (which checks for coding style and warnings, see Style guide). These checks are ran automatically using GitHub Actions. You also need at least one approval from a maintainer - after submitting a PR, you can request a review from the top right Reviewers menu on the Pull Request page.

Branches

Please use the following structure for creating new development branches:

[username]/[snake_case_branch_name]

This will make branches easier to find and search for.
Examples:

razvanra2/add_authoring_policy
luigi/dev_eng_improv
auditore/fix_md_typo
torvalds/android_speedups

Merging

When developing a new feature or working on a bug, your pull request will end up containing fix-up commits (commits that change the same line of code repeatedly) or too fine-grained commits. An issue that can arise from this is that the main branch history will become polluted with unnecessary commits. To avoid it, we implement and enforce a squash policy. All commits that are merged into the main development branch have to be squashed ahead of the merge. You can do so by pressing "squash and merge" in GitHub (recommended), or, alternatively, following the generic local squash routine outlined bellow:

git checkout your_branch_name
git rebase -i HEAD~n
# n is normally the number of commits in the pull request.
# Set commits (except the one in the first line) from 'pick' to 'squash', save and quit.
# On the next screen, edit/refine commit messages.
# Save and quit.
git push -f # (force push to GitHub)

Please update the resulting commit message, if needed. It should read as a coherent message. In most cases, this means not just listing the interim commits.

Please refrain from creating several pull requests for the same change. Use the pull request that is already open (or was created earlier) to amend changes. This preserves the discussion and review that happened earlier for the respective change set. Similarly, please create one PR per development item, instead of bundling multiple fixes and improvements in a single PR.

⚠️ After the merging is concluded, please delete the branches related to the pull request that you just closed.

Bugs & features

Future improvements on the app are tracked in Github's Projects and Issues. Use Projects for bugs which don't affect the normal usage of the app or less important features. Major bugs and more relevant features are tracked on Issues. Keep in mind that you can convert a Project into an Issue if necessary (e.g. if you start working on it and want to have it assigned or associated with a PR).

When mentioning an issue in code using a TODO comment, consider using the format: TODO(GitHub username): Description, #issueID

As mentioned in flutter_style_todos lint, the person mentioned in the TODO is the person most familiar with the issue, and not necesarily the one who is assigned to solve it.

Development tips

  • Make sure you have the Project view open in the Project tab on the left in Android Studio (not Android).
  • Flutter comes with Hot Reload (the lightning icon, or Ctrl+\ or ⌘\), which allows you to load changes in the code quickly into an already running app, without you needing to reinstall it. It's a very handy feature, but it doesn't work all the time - if you change the code, use Hot Reload but don't see the expected changes, or see some weird behaviour, you may need to close and restart the app (or even reinstall).
  • If running on web doesn't give the expected results after changing some code, you may need to clear the cache (in Chrome: Ctrl+Shift+C or ⌘+Shift+C to open the Inspect menu, then right-click the Refresh button, and select Empty cache and Hard reload.)
  • Flutter Inspector is a powerful tool which allows you to visualize and explore Flutter widget trees. You can use it to find out where a specific part of the UI is defined in the code (by turning on Select widget mode and selecting the widget you'd like to find), it can help you debug layouts (by enabling Debug Paint, you can visualize padding, alignments and widget borders) and much more.
    • If Flutter Inspector doesn't work when running on web, a possible solution is to disable embedding DevTools (IntelliJ/Android Studio: File > Settings > Languages & Frameworks > Flutter > Enable embedding DevTools in the Flutter Inspector tool window).
  • Get used to reading code and searching through the codebase. The project is fairly large and for most things, you should be able to find a usage/implementation example within our codebase. Do try to reuse code as much as possible - prefer creating a customizable widget with the parameters you need for different use cases, over copy-pasting widget code. Some tips for exploring the codebase:
    • Ctrl+Shift+F or ⌘+Shift+F upon clicking a directory in the Project view lets you search a keyword through the entire directory. This is particularly useful for searching for something in the entire codebase.
    • Ctrl+click or ⌘+click through a class/method name takes you to its definition. You can also right-click on it and use "Find usages" or the various "Go To" options to explore how it is used/defined.
      • Oftentimes, a class definition will be in a file that is outside our project - either from a package or the Flutter framework itself. These files are marked with a yellow background in the Android Studio tab bar. You cannot edit them, but it's often useful to read through them to understand how they work.

reading-code

  • By default, Flutter apps run in debug mode. That means a DEBUG banner is shown on the upper right corner of the app, and errors and overflows are marked quite visibly in the UI.

    • If you'd like to temporarily hide the debug mode banner (to take a screenshot, for instance), open the Flutter Inspector tab from the right hand edge of Android Studio, click More actions and select Hide debug mode banner.
    • Note that Flutter's debug mode is different from the Android Studio debugging (bug button), which is meant to allow you to use breakpoints and other debugging tools.
    • If you really need to test the release version (usually not necessary), run flutter run --release from the Terminal.
      • The release version cannot be ran in an emulator.
      • You may also need to temporarily change the release signing config. In the android/app/build.gradle file, replace signingConfig signingConfigs.release with signingConfig signingConfigs.debug.
      • To switch to debug config on web, in the web/index.html file, replace firebaseConfig.release with firebaseConfig.debug.
      • For simplicity, you could call the default "main.dart" configuration in Android Studio "Debug", duplicate it and call the second one "Release", with --release as an argument. For example:
  • On Android, ACS UPB Mobile uses a separate (development) environment in debug mode. That means a completely different Firebase project - separate data, including user info. Some important notes:

    • This is not used automatically on iOS and web (#105), but there are some solutions. On web, you can manually switch to the dev environment by replacing firebaseConfig.release with firebaseConfig.debug in the web/index.html file. On iOS, you can test the app in debug mode by running ./run-ios-flavor.sh dev in the root folder of the project. Similarly, if you need to run the release version (not recommended), you can run ./run-ios-flavor.sh prod. You can find more information about build flavors in Flutter here.
    • If you want to copy the data from the production environment into the development environment, you need to follow the export/import instructions from the Firebase docs. This can help debug builds to match prod better, but this should NEVER be used to copy user data. Everything in the users/ collection should stay on the production project (and it would not work anyway, since the user IDs don't match the users in dev).
    You should ALWAYS use the separate development environment for testing the app when modifying any kind of data, so as not to risk breaking something in the production database.

Style guide

This project uses the official Dart style guide with the following mentions:

  • Android Studio (IntelliJ) with the dartfmt tool is used to automatically format the code (Ctrl+Alt+L or ⌥+⌘+L), including the order of imports (Ctrl+Alt+O or ⌥+⌘+O).
  • The extra_pedantic package is used for static analysis: it automatically highlights warnings related to the recommended dart style. Most of them can be fixed automatically by invoking Context Actions (place the cursor on the warning and press Alt+Enter) and selecting the correct action. Do not suppress a warning unless you know what you're doing - if you don't know how to fix it and Android Studio doesn't help, hover over the warning and you'll see a link to the documentation that can help you understand.
  • Where necessary, comments should use Markdown formatting (e.g. `backticks` for code snippets and [brackets] for code references).

GitHub Actions

This project uses GitHub Actions for CI/CD. That means that testing and deployment are automated.

The following actions are currently set up:

  • Linter: Checks for typos, warnings and coding style issues based on the Dangerfile. Runs on every push and pull request.
    • If your PR is made from a branch inside the repository (rather than a fork), which is the preferred way to make contributions, acs-upb-mobile-bot should automatically add code review comments pointing out any warnings.
      • Sometimes, the automatic check for dead links in documentation fails with "429 too many requests" (see this issue). You can ignore those if you know the links in question are good.
      • Do not ask the bot for review, it does it automatically.
    • If you have formatting issues, the "Check formatting" step will point out the files that need to be formatted and the workflow will fail.
  • Tests: Runs all tests in the test/ directory and submits a coverage report to codecov. This action is triggered on every push and pull request.
    • If at least one test fails, this workflow will fail.
    • The coverage is the percentage of lines of code that are executed at least once in tests. This project aims to keep coverage above 70% at all times.
  • Deployment: Deploys the app to Google Play, as well as the website (acs-upb-mobile.web.app), and creates a corresponding GitHub Release including the APK. It also deploys Firebase functions as well as Firestore security rules and indexes (see Working with Firebase for more info). This action is triggered when a new version tag (e.g. v1.2.10) is pushed.
    • Do not push version tags unless you know what you are doing. If you do push a tag, you should annotate it (e.g. use a command like git tag -a v1.2.10) with the content of the en-US changelog file. The first line of the annotation should be the version (e.g. v1.2.10), followed by an empty line, followed by the content of the English changelog.
    • For the Google Play release, we are using fastlane. It requires creating changelog files in each supported language under android/fastlane/metadata/android, with the name $(pubspec_build_number + 10000).txt. For example, if the version in pubspec.yaml is 1.2.10+12, the files should be named 100012.txt. The content of the changelog file is what the users will se under the "What's new" section in Google Play, and should use a friendly language and generally be organised by sections like Added, Fixed, Improved etc. Look at the existing changelogs for examples.

Working with Firebase

ACS UPB Mobile uses Firebase - an app development platform built on Google infrastructure and using Google Cloud Platform - to manage remote storage and authentication, as well as other cloud resources. We have two separate projects for the app - one for production (acs-upb-mobile) and one for development (acs-upb-mobile-dev). The former is the "real" app with actual user data, and we use the latter when developing and adding new features to avoid causing disturbances in the production environment that users could notice. You can ask a maintainer to gain access to the development project, and in most cases you shouldn't need access to the production project.

Setup

This application uses flutterfire plugins in order to access Firebase services. They are already enabled in the pubspec file and ready to import and use in the code.

❗ FlutterFire only has Cloud Storage support for Android and iOS. The web version needs a special implementation. See resources/storage for an example.

Firebase projects can be managed through the Firebase console and the Firebase CLI. To set up the CLI tools, follow the official instructions. You should then authenticate using firebase login. You DO NOT need to run firebase init, as the project is already initialized in the repo.

Authentication

Firebase provides an entire suite of back-end services and SDKs for authenticating users within an application, through FirebaseAuth.

For our application, we use the following features:

  • account creation
  • login
  • password reset via e-mail
  • account verification via e-mail
  • account deletion

Firebase Authentication stores user information such as UID, email, name and password hash. In order to store additional information such as user preferences and group, when a new user signs up, a corresponding document is created in the users collection.

This service automatically handles the authentication tokens and enforces security rules, which is particularly useful for an open-source application which users can fiddle with, such as ours. For example, multiple failed authentication attempts lead to a temporary timeout, and a user cannot delete their account unless they have logged in very recently (or refreshed their authentication token).

Firestore security rules can be enforced based on the user’s UID. This method means that, even though users can access the database connection string through the public repository, they can only do a limited set of actions on the database, depending on whether they are authenticated and their permissions.

Firestore

Cloud Firestore is a noSQL database that organises its data in collections and documents.

Data model

Collections are simply a list of documents, where each document has an ID within the collection.

Documents are similar to a JSON file (or a C struct, if you prefer), in that they contain different fields which have three important components: a name - what we use to refer to the field, similar to a dictionary key -, a type (which can be one of string, number, boolean, map, array, null - yeah null is its own type -, timestamp, geopoint, reference - sort of like a pointer to another document), and the actual value, the data contained in the field. In addition to fields, documents can contain collections… which contain other documents… which can contain collections, and so on and so forth, allowing us to create a hierarchical structure within the database.

More information about the Firestore data model can be found here.

Security rules

Firestore allows for defining specific security rules for each collection. Rules can be applied for each different type of transaction - reads (where single-document reads - get - and queries - list - can have different rules) and writes (where create, delete and update can be treated separately).

More information on Firestore security rules can be found here.

You can update security rules directly from the Firebase console (select Firestore Database from the side menu, then click the "Rules" tab). However, that can lead to conflicts when we deploy the rules from firestore.rules. Always remember to update the repo file as well, otherwise you risk your changes being overwritten. As of Aug 2021, there is no CLI command to fetch the current rules, so you have to manually copy them from the console to the file.

The preferred method to update security rules is through the Firebase CLI (see section Setup above). Simply edit the firestore.rules file, then deploy the rules using the following commands:

firebase use dev # Make sure the dev environment is selected
firebase deploy --only firestore:rules

You do not need to worry about deploying to the production environment, as that is handled by the automated scripts that run when you merge a PR (see GitHub Actions).

Indexes

Indexes are useful to improve query performance, and Firestore requires an index for every query. Basic indexes are already generated, and when you run a query that requires a new index, you will get an error and a link that will walk you through creating the necessary indexes.

❗ When you create a new index from the Firebase console, you must update the firestore.indexes.json file by calling:

firebase firestore:indexes > firestore.indexes.json

Project database

The project database contains the following collections:

users This collection stores per-user data. The document key is the user's `uid` (from FirebaseAuth).
Fields

All the documents in the collection share the same structure:

Field Type Required? Additional info
group string 🗹 e.g. “314CB”
name map<string, string> 🗹 keys are “first” and “last”
permissionLevel number a numeric value that defines what the user is allowed to do; if missing, it is treated as being equal to zero
Sub-collections
  • websites
    A user can define their own websites, that only they have access to. These will reside in the websites sub-collection, and have the following field structure, similar to the one in the root-level websites collection:
Field Type Required? Additional info
category string 🗹 one of: “learning”, “association”, “administrative”, “resource”, “other”
icon string path in Firebase Storage; if missing, it defaults to "icons/websites/globe.png"
label string 🗹 unless specified, the app sets this to be the link without the protocol
link string 🗹 it needs to include the protocol
Rules

Anyone can create a new user (a new document in this collection) if the permissionLevel of the created user is 0, null or not set at all.

Authenticated users can only read, delete and update their own document (including its sub-collections) and no one else's. However, they cannot modify the permissionLevel field.

websites This collection stores useful websites, shown in the app under the *Portal* page. Who they are relevant for depends on the `degree` and `relevance` fields (for more information, see the filters collection).
Fields

All the documents in the collection share the same structure:

Field Type Required? Additional info
addedBy string 🗹 ID of user who created this website
category string 🗹 one of: “learning”, “association”, “administrative”, “resource”, “other”
degree string “BSc” or “MSc”, must be specified if relevance is not *null*
editedBy array<string> list of user IDs
icon string path in Firebase Storage; if missing, it defaults to "icons/websites/globe.png"
label string 🗹 unless specified, the app sets this to be the link without the protocol
link string 🗹 it needs to include the protocol
relevance null / list<string> 🗹 *null* if relevant for everyone, otherwise a string of filter node names
Rules

Since websites in this collection are public information (anyone can read), altering and adding data here is a privilege and needs to be monitored, therefore anyone who wants to modify this data needs to be authenticated in the first place.

Users can create a new public website only if their permissionLevel is equal to or greater than three and they sign the data by putting their uid in the addedBy field.

Users can update a website if they do not modify the addedBy field and they sign the modification by adding their uid at the end of the editedBy list.

Users can only delete a website if they are the ones who created it (their uid is equal to the addedBy field) or if their permissionLevel is equal to or greater than four.

filters This collection stores Filter objects. These are basically trees with named nodes and levels. In the case of the relevance filter, they are meant to represent the way the University organises students:
                                  All
                    _______________|_______________
                  /                                \
                BSc                               MSc       // Degree
         ________|________                 ________|__ …
       /                  \              /     |
      IS                 CTI            IA   SPRC …       // Specialization
   …|…          ______|______       ⋮      ⋮
                  /    |     |   \
               CTI-1 CTI-2 CTI-3 CTI-4                      // Year
                  ⋮    ⋮   __|… ⋮
                        /   |
                     3-CA 3-CB …                          // Series
                     __|…
                   /   |
               331CA 332CA …                              // Group
Fields

All the documents in the collection share the same structure:

Field Type Required? Additional info
levelNames array<map<string, string>> 🗹 localized names for each tree level (e.g. "Year"); the map keys are the locale strings ("en", "ro")
root map<string, map<string, map<…>>> 🗹 nested map representing the tree structure, where the key is the name of the node and the value is a map of its children; the leaf nodes have an empty map as a value, **not** *null* or something else
Rules

Filter structure is public information and should never (or very rarely) need to be modified, therefore for this collection, anyone can read but no one can write.

import_moodle This collection contains class data imported directly from the University's Moodle instance. The data is exported as a spreadsheet from Moodle, and imported to our app's Firestore using a Node.js script. Additional information about classes is stored in the classes collection.
Fields

The structure of the documents in the collection is the same as the columns in the export file:


All of the fields are strings. In the app, shortname is used to extract the class' acronym, fullname is the class' name, and category_path defines the category under which the class is listed on the ClassesPage.

Rules

This is public information already available on Moodle, and will never be editable directly through the app. Therefore for this collection, anyone can read but no one can write.

classes This collection stores information about classes defined in the import_moodle collection. The ID of a document in this collection corresponds to the shortname of a document in import_moodle.
Fields

All the documents in the collection share the same structure:

Field Type Required? Additional info
grading map<string, number> map where the key is the name of the evaluation (e.g. “Exam”) and the value is the number of points that specific evaluation weighs (generally out of 10 total)
lecturer string the ID of a person in the people collection
shortcuts array<map<string, string>> array of maps representing relevant links for a class, similar to websites; map keys are "addedBy", "link", "name", and "type", with "type" being one of "main", "classbook", "resource" and "other"
Rules

Since classes in this collection are public information (anyone can read), altering and adding data here is a privilege and needs to be monitored, therefore anyone who wants to modify this data needs to be authenticated in the first place.

Users can update an existing class document if their permissionLevel is equal to or greater than three. Additionally, they can only create a new class document if a document with that specific ID exists in the import_moodle collection.

Documents in this collection cannot be deleted.

people This collection currently contains information about faculty staff, extracted from the official website using a Python scraper.
Fields

All the documents in the collection share the same structure:

Field Type Required? Additional info
email string 🗹
name string 🗹
office string 🗹
phone string 🗹
photo string 🗹 a link to the person's photo
position string 🗹 the person's position within the faculty, e.g. "Professor, Dr."
Rules

This is public information already available on the official website, and currently cannot be edited through the app due to privacy concerns. Therefore for this collection, anyone can read but no one can write.

calendars This collection stores academic calendar information, used to calculate recurrences of events in the events collection. This information is generally posted yearly on the university website (for example here for the year 2020).

The ID of a document in this collection is the academic year it corresponds to (for academic year 2020-2021, the ID is simply "2020").

Fields

All the documents in the collection share the same structure:

Field Type Required? Additional info
exams array<event*> 🗹 exam sessions defined in the academic year
holidays array<event*> 🗹 holiday intervals defined in the academic year
semester array<event*> 🗹 semesters defined in the academic year, in chronological order

*An event is a map<string, dynamic> with the following fields:

Field Type Required? Additional info
name string The name of the time interval defined in the calendar. This needs to be specified for everything but semesters, where the name is automatically set as the index of the event in the list (starting from 1).
start timestamp 🗹 The first day of the interval. The hour is irrelevant, it should be set to 00:00.
end timestamp 🗹 The last day of the interval. The hour is irrelevant, it should be set to 00:00.
degree string “BSc” or “MSc”, must be specified if relevance is specified and not null
relevance null / list<string> null if relevant for everyone, otherwise a string of filter node names
Rules

Academic calendars are public information and should never (or very rarely) need to be modified, therefore for this collection, anyone can read but no one can write.

events This collection stores timetable events, shown in the app under the *Timetable* page.
Fields

All the documents in the collection share the same structure:

Field Type Required? Additional info
addedBy string 🗹 ID of user who created this website
editedBy array<string> 🗹 list of user IDs of users who edited this event
calendar string 🗹 ID of a calendar in the calendars collection; it is used to calculate recurrences and skip holidays correctly
class string 🗹 ID of a class in the classes collection
name string Optional event name. If this is not specified, the class acronym will be used instead.
start timestamp 🗹 The first instance of the event.
duration map 🗹 A map containing duration keys like "hours", "minutes" etc.
location string Optional event location, if applicable.
type string 🗹 One of "lecture", "lab", "seminar", "sports", "other"
rrule string If the event repeats, a recurrence rule in the format defined in RFC-5543, for example "RRULE:FREQ=WEEKLY;UNTIL=20210131T000000;INTERVAL=2;BYDAY=TH" for an event that repeats every second Thursday until Jan 31st 2021
degree string “BSc” or “MSc”, must be specified if relevance is specified and not null
relevance null / list<string> null if relevant for everyone, otherwise a string of filter node names
Rules

Since events in this collection are public information (anyone can read), altering and adding data here is a privilege and needs to be monitored, therefore anyone who wants to modify this data needs to be authenticated in the first place.

Users can create a new event only if their permissionLevel is equal to or greater than three and they sign the data by putting their uid in the addedBy field.

Users can update an event if they do not modify the addedBy field and they sign the modification by adding their uid at the end of the editedBy list.

Users can only delete an event if they are the ones who created it (their uid is equal to the addedBy field) or if their permissionLevel is equal to or greater than four.

Storage

Cloud Storage complements Firestore by allowing storage of binary files, such as photos and videos.

Structure

The Cloud Storage is structured in directories and files (also referred to as objects), much like any other type of storage. These are placed inside a bucket - the basic container that holds data. You can think of buckets like a physical storage device - they have a location (and their own security rules and permissions), and unlike directories, cannot be nested.

The main bucket of the app (acs-upb-mobile.appspot.com) can be accessed via the Firebase console. It contains app resources such as icons and profile pictures, organised similarly to the data in Firestore:

  • Website icons are stored in the websites/ directory. The icon of a website in Firestore that has the ID "abcd" will be in storage under websites/abcd/icon.png.
  • Profile pictures are stored in the users/ directory. The picture of a user with the UID "abcd" would be in storage under users/abcd/picture.png.

Security rules

Storage security rules are similar to Firestore security rules. One of the reasons for keeping the storage structure as close as possible to the Firestore structure is the ability to have similar security rules (for example, if, in Firestore, a user can only access their own document, the same rule can be applied for a user's folder inside Storage).

More information on Storage security rules can be found here.

You can update security rules directly from the Firebase console (select Storage from the side menu, then click the "Rules" tab). However, that can lead to conflicts when we deploy the rules from storage.rules. Always remember to update the repo file as well, otherwise you risk your changes being overwritten. As of Aug 2021, there is no CLI command to fetch the current rules, so you have to manually copy them from the console to the file.

The preferred method to update security rules is through the Firebase CLI (see section Setup above). Simply edit the storage.rules file, then deploy the rules using the following commands:

firebase use dev # Make sure the dev environment is selected
firebase deploy --only storage

You do not need to worry about deploying to the production environment, as that is handled by the automated scripts that run when you merge a PR (see GitHub Actions).

Functions

Cloud Functions for Firebase is a serverless solution for running bits of code in response to events or at scheduled time intervals. They are, for all intents and purposes, JavaScript/TypeScript functions that run directly "in the cloud", without needing to be tied to an app or device.

The project currently has two functions set up to perform daily backups of the data in Firestore (backupFirestore) and Storage (backupStorage). They are scheduled to run automatically, every day at 00:00 EEST.

The Functions page in the Firebase console offers a useful overview of the functions and their logs, but for more details and control you can also view them in the Cloud Functions dashboard of the Google Cloud Console.

Testing

Usually, cloud functions can be tested locally using the Firebase emulator. However, since our backup functions are using Pub/Sub & Cloud Scheduler to run at scheduled times, they are a bit trickier to test using the emulator. They can, however, be tested on the dev project by deploying them manually through the Firebase CLI.

To do that, you first need to create a service account key by going to the Firebase console of the development project, pressing the cogwheel, then "Users and permissions". On the "Service accounts" tab under "Firebase Admin SDK", make sure "Node.js" is selected and press "Generate new service key". The generated file needs to be named "serviceAccountKey.json" and placed in the functions/ folder of your local copy of the repository. Note that this file is currently used only by backupStorage, as backupFirestore automatically uses the default service account (docs).

Next, to deploy the functions to the dev project, you need to run:

# IMPORTANT: Switch to the dev project
firebase use dev
# Deploy local functions/.
firebase deploy --only functions
# Alternatively, you can also deploy a specific function using, e.g.:
# firebase deploy --only functions:backupFirestore

NOTE: Recent npm versions seem to have an issue with the --prefix flag not working on Windows (older issue, newer issue). If you're getting an ENOENT error saying that acs_upb_mobile\package.json cannot be found, try replacing --prefix with --cwd in firebase.json.

You can then go to the Firebase console under Functions, press the three dots next to one of the functions and select "View in Cloud Scheduler". In this dashboard, you can trigger the functions manually when needed.

Internationalization

On-device

All strings that are visible to the user should be internationalised and set in the corresponding .arb files within the l10n folder. The Flutter Intl Android Studio plugin does all the hard work for you by generating the code when you save an .arb file. Strings can then be accessed using S.current.stringID.

Remote

In the database, internationalized strings are saved as a dictionary where the locale is the key:

{
    'ro': 'Îmi place Flutter!',
    'en': 'I like Flutter!'
}

These will have a corresponding Map variable in the Dart code (e.g. Map<String, String> infoByLocale). See WebsiteProvider for a serialization/deserialization example.

Changing the locale

Changing the app's language is done via the settings page.

Fetching the locale

The LocaleProvider class offers utility methods for fetching the current locale string. See PortalPage for a usage example.

Custom icons

If you need to use icons other than the ones provided by the Material library or Feather Icons (accessible directly in the code through the Icons and FeatherIcons classes respectively), the process is as follows:

Generating the font file

  • Convert the .ttf custom font in the project to an .svg font (using a tool such as this one).
  • Go to FlutterIcon and upload (drag & drop) the file you obtained earlier in order to import the icons.
  • Check that the imported icons are the ones defined in the CustomIcons class to make sure nothing went wrong with the conversion, and select all of them.
  • (Upload and) select any additional icons that you want to use in the project, then click Download.

Updating the project

  • Rename the font file in the archive downloaded earlier to CustomIcons.ttf and replace the custom font in the project.
  • Copy the IconData definitions from the .dart file in the archive and replace the corresponding definitions in the CustomIcons class;
  • Check that everything still works correctly :)

NOTE: FontAwesome outline icons are recommended, where possible, because they are consistent with the overall style. For additional action icons check out FontAwesomeActions - the repo provides an .svg font you can upload directly into FlutterIcon.