๐๐๐ Apollo 11 ๐๐๐
Let It Go is an app that allows you to locate toilets in NUS nearest to you in a jiffy. Apart from this, this app aims to provide a comprehensive set of information on each toilet to ensure the user's demands are well taken care of. With a Google Reviews style, users can also submit reviews regarding each toilet and give it a rating.
We come in both a Web App and an iOS App. With the iOS being a watered down and minimalist version of its web counterpart.
Never fear again when you are in a foreign part of NUS, for now you can finally release your faeces in peace!
And yes, you can submit your own toilets too! (Learn more)
Web App: https://nelsontky.github.io/let-it-go/
Submissions portal (?): https://nelsontky.github.io/let-it-go-submission/
Demo video: https://youtu.be/YADOVkDN8HE
- All toilets in Computing and some in Business are present in the app!
- Map showing the location of toilet.
- 360 degree photos of the vicinity of each toilet to facilitate recognition and navigation to a toilet.
- Detailed information about each toilet, such as the availability of facilities like wheelchair accessible cubicles or presence of shower heads.
- Ability to filter/sort toilets by features such as proximity (duh!) and gender.
- Review system that is linked to Google account.
- Each Google account can only submit 1 review, with the ability to edit or delete the review.
- Ability to rate toilets with stars, average stars are shown at the top.
- Reviews are paginated, preventing reviews from taking too much page space.
- Clean and Minimalistic display
- Share toilets to social media!
- Separate Web Portal that allows users to submit toilets, along with functionalities of editing and deleting Toilets will be added into main database upon review.
- Implemented as a separate webapp, more information about the submissions portal can be found here.
- Basically anyone in NUS. Even students/professors who already know where the nearest toilets are can still benefit.
- Cleaners in NUS. With our review system in place, resources for cleaners can be activated more efficiently.
- GatsbyJS
- Firebase
- React Native
- And lots and lots of love ๐๐๐
Just like anyone who is exploring new technology that he/she has never used before, we definitely ran into many problems when starting out with GatsbyJS, Firebase, Git, and React Native.
- GatsbyJS
- It was difficult to get Javascript libraries (such as the Google Maps api) to work with ReactJS (and consequently GatsbyJS)
- I've been told that there are many npm modules made as replacements/wrappers for Javascript libraries. (There are like 10 million npm modules that implement the Google Maps api. Some examples can be found: here, here, and here)
- However, such unofficial modules do not have the best documentation and are plagued with multiple issues and unanswered stackoverflow threads.
- Thus, so as to enjoy the extensive documentation that comes with official apis, we decided to employ hacky means to make Javascript libraries work in ReactJS. There were many problems and performance issues initially but thank god, there were more answered stackoverflow threads on how to integrate Javascript apis into ReactJS than those on the unofficial ReactJS wrapper modules.
- We eventually managed to fix every memory leak and rogue re-render brought by our sloppy implementations of Javascript apis and such a task gave us more understanding of React lifecycles.
- Other than that, GatsbyJS was a pleasure to work with and the Firebase integration with GraphQL was intuitive and simple. ๐
- Firebase
- We used a firebase database to store information about the toilets in NUS. (Subsequently, we added user reviews and user submissions to the database too!)
- Firebase seemed flashy enough to get our project certified for ๐๐๐ Apollo 11 ๐๐๐ and that's why we went for it!
- And of course not forgetting its nifty integration with multiple login providers, thus allowing us an easy way to implement user authentication!
- When asked if we wanted to use the older Realtime Database or the bleeding edge Cloud Firestore, our inner vampire appeared and we went for bleeding edge.
- Regretted our decision in the beginning. Documentation (and stackoverflow threads) for the newer storage model was sparse and simple functions such as backup and restore of databases required us to jump through so many hoops. ๐ญ
- We could not understand the revolutionary data model of collections and documents. It was totally different from the
{ key: value }
JSON models that we were used to. - Slowly but surely, we came to appreciate Cloud Firestore's data model, and now, there are no more hard feelings. ๐
- It's still inconvenient to create backups and thus we wrote a bunch of scripts that could work well with our database structure so as to export our database as a JSON. ๐ Not the best way but we work with what we have.
- Authentication with Firebase was easy to work with. In fact, it was so easy that we felt dirty and doubted if such an implementation could bring us ๐๐๐ Apollo 11 ๐๐๐ glory.
- Only problem was that the firebase-auth-ui react wrapper did not play nice with GatsbyJS.
- In the end we decided to use hacky methods to implement the Javascript api into GatsbyJS, which worked pleasantly.
- This was really hacky, we did dirty things with constructors and class instances. ๐๐๐
- All in all, Firebase was nice to work with and the lessons learnt from building something with Firebase can be employed to build scalable apps that do not require us to bother ourselves too much with creating a database/backend from scratch.
- We used a firebase database to store information about the toilets in NUS. (Subsequently, we added user reviews and user submissions to the database too!)
- Git
- We were using Git (and Github) for the sake of attaining Apollo 11 certification back then and thus did not really appreciate its usefulness.
- As I am typing this one day before milestone 3 submission deadline, ๐๐๐ I realised how important each commit message was and most of the things typed above came from memories triggered by our old commit messages.
- One thing that led to us not appreciating Git was that we did not use it to collaborate. (as you can see - all commits, except 1 - in this repo was made by Nelson ๐)
- We started trying to collaborate with Git while writing our submissions portal. We experimented with all that flashy branching and merging thingies and of course, we ran into the issue of merge conflicts, which will be described in the submission portal's README.
- Git was also useful when we wanted to roll back to old versions of code but once I rolled back to the wrong commit and chaos ensued :(
- From that unfortunate incident, we learnt to commit more frequently and commit individual files as much as possible. Before this, we were committing huge chunks of code and multiple files only at the end of a hearty day of coding. ๐ ๐ ๐
- Usage of Github gave us trouble when I accidentally pushed private api keys into this very public repo. ๐ It was almost impossible to rewrite history in git and it is also discouraged to do so. More of this issue will be elaborated in the milestone 3 section.
- Git is a very useful tool for managing large codebases and we definitely will be using it in any other projects we are involved in. We will just try not to do stupid things. ๐
- ๐โโ๏ธ๐โโ๏ธ๐โโ๏ธ FEMALE TOILETS ๐โโ๏ธ๐โโ๏ธ๐โโ๏ธ
- There was no way an orbital group composed of 2 guys could enter female toilets to find out which facilities they had. And there is only so many times we can ask a female for help.
- So we decided to create a submissions portal to excuse our inability to document female toilets.
- We eventually used the submissions portal as an excuse to not document all the toilets in NUS too ๐ and decided to spend more time on the submissions portal to make it more polished.
- We learnt a lot from the submissions portal too. A similar section as this can be found in the submission portal's README.
- React Native
- While there were a lot of helpful documentation on React Native readily available online, there were issues we met along the way due to the relatively young history of React Native, resulting in numerous unexpected bugs.
- Bug reportings on React Native were generally crpytic in nature, for example, a simple error of not running
react-native link
ensued in a lot of unnecessary confusion. - We came to discover that many packages/modules widely available for React Native were not well maintained and had nagging issues for either iOS/Android side, leading to the us taking the wise choice of specialising for iOS only, allowing better performance and less unexpected bugs
- It also did not help that different modules had different integration steps with Xcode, throwing a lot of unnecessary warnings and issues that could potentially result in bugs in future.
All in all, milestone 1 + 2 was exciting as we were just blind kids experimenting with new technologies and blundering around copying stackoverflow answers with little to no understanding. Slowly yet surely, we came to understand what we were actually doing and actually came up with something stable by milestone 2. The best way to learn is to fail, and what better way is there to learn other than by reading countless pages of documentation and stackoverflow pages till our app started working? Moving fast and breaking things definitely gave us a better appreciation of the technologies we employed and by the end of milestone 2, we were even good enough to help other Orbital mates fix some of the bugs in their code! ๐๐๐
Milestone 3 was one hell of a ride. The move fast and break things mindset we had in the earlier milestones came back to bite us hard as some deep rooted issues that we waved away back then surfaced. ๐ Here are some issues that actually came back and bit us.
-
Keys and security
- Api key management and database security was always this nagging thought in our minds.
- However, we dispelled all such thoughts in the earlier milestones as we were adopting a move fast and break things stance and why let boring concepts like key security and database rules slow us down?
- For some reason, a few days after milestone 2, I decided to rename our Firebase Admin SDK private key. This led to me accidentally pushing our private keys to this very public repo (I did not update the
.gitignore
file, nor did I check the committed files before pushing ๐), which led to this very scary email from Google:
- In this day and age, no one actually checks their emails and thus I did not identify any problems and in less than 2 hours, I received this scarier email: (I mean IT'S IN RED)
- The problem was only identified when reviews suddenly would not work and I started checking my emails and then ๐คฆโโ๏ธ๐คฆโโ๏ธ๐คฆโโ๏ธ.
- Apparently, bots crawl public Github repos for Google api keys and then use them to mine cryptocurrency with a Google Cloud Compute instance using the scraped api key.
- First we submitted appeals to Google to get our database back. Subsequently, we tried to rewrite git history by deleting all sensitive information.
- The next day, our appeal was still not processed and with us raring and ready to code, we decided to create a new firebase project and restore one of our nifty JSON backups to the new project.
- Such a decision was actually a master stroke as it allowed us to delete the old project, along with all the other compromised api keys.
- We no longer had to rewrite git history. ๐
- With a fresh start, we decided not to mess it up again and started reading up about environment variables and learning how to secure and lock down production keys while still allowing development keys full access.
- No more fear when we received an email like this, for we've locked down our production key to only be able to be used from a single domain.
- We could've made the keys more secure and harder to be scrapped by employing Base64 encoding but we do not think that it is very necessary.
- As can be seen from the numerous commits where we thought that things were fixed, this issue of key security was quite a ride. ๐
- We are glad that we learnt about key security while doing a small scale project like Orbital. Locking down production keys is paramount to security and we've learnt that we should always check committed files before pushing them to Github. Think of the consequences if we do accidentally push private keys during our internship/work. :shudders:
- Api key management and database security was always this nagging thought in our minds.
-
Database security
- With Cloud Firestore's revolutionary data model (as alluded to in the previous segment), the way we organise data must be carefully considered so as to allow for easier integration of database security rules.
- Our move fast and break things approach did us no favors here and we had to restructure the
reviews
collection so as to ensure that logged in users could only edit and delete their own reviews. - After much restructuring, our
reviews
collections looks like this:
(:scroll: represents a collection while :page_facing_up: represents a document)
- ๐
reviews
- ๐
COM1 Level 1 Main Entrance
- ๐
users
- ๐
userId
(a random unique id that is generated for every user who has logged in before) review :
date :
name :
photoURL :
review :
score :
uid :
- ๐
Another userId
...
- ๐
Other toilets
...
- In the
reviews
collection, each toilet has their own specific document (named after the toilet name). Inside each toilet document, there is a collection ofusers
who've posted reviews. - Such a structure then allows us to lock down the database such that only authors of a review can delete/edit their review. Code that secures the
reviews
collection as such is shown below:
match /reviews/{toilet}/users/{userId} {
allow read: if true;
// Only allow users to write to what they created
allow write: if request.auth.uid == userId;
}
- From this, we learnt that we should always start structuring data with security in mind, thus reducing the amount of data we have to restructure once we begin locking down the database. ๐
Bug hunting and user testing was also something we waited till milestone 3 to start seriously doing. We gave up our daily dose of Hacker News on our commutes and started vigorously testing the application in our free time.
-
Location services related testing
-
We spent a lot of time obsessing about what would happen when users do funny things with their GPS settings. What would happen if a user turns off their GPS after navigating to a new page in the webapp? What will happen if the only enable GPS after navigating to a new page?
- For this, we came up with a tooltip that would show when a user's location was not detected. Subsequently, we tried all the weird combinations of enabling/disabling location services that are possible and settled on a reliable algorithm which allowed the tooltip to be shown whenever it should appear.
Tooltip only shows when location cannot be detected by the browser. A helpful message is also shown upon clicking on the question mark icon to aid users in enabling location services.
(040c6ba and 83c51f1 were 2 commits that made the tooltip more reliable after much testing)
-
On our commutes, we also tested the location services to check if our location marker would update while we were moving. Preliminary testing showed that the location updates were too choppy and unreliable.
- Back then, we were using an algorithm that would grab a user's location via
Geolocation.getCurrentPosition()
every second and then update it on the map. - After the testing yielded less than satisfactory results, we went back to the old trusty Google and realised that there exists a command that updates the user location whenever position of the device changes.
- Changing to using
Geolocation.watchPosition()
in commit b20af76 made location polling waaaaaaaayyyy smoother. - Plus, performance improved by a lot as the per second polling employed back then led to memory leaks, slowing an eighth gen Intel Core i5 laptop to a crawl. ๐
- Back then, we were using an algorithm that would grab a user's location via
-
-
Panorama performance issues
- As stated above, some of our implementations of Javascript apis in ReactJS left much to be desired. Our implementation of the Pannellum library was creating multiple viewers without destruction, thus causing performance issues. This was fixed in 79dcd8e, after reading more on the Pannellum docs and after gaining more understanding of React lifecycle methods.
-
Spelling woes (Not really a bug but yes, a bug in our brains LOL)
- One fine day as we let one of our friends try the webapp, this sharp boy majoring in English saw that all instances of "separate" was spelt wrongly as "seperate".
- First there was denial. "IT'S JUST THE BRITISH SPELLING!", I argued with blind fervour.
- However, a quick Google search led to the conclusion that my 22 years on this Earth was a lie and that most people start spelling "separate" correctly when they are 6.
- This led to us renaming all instances of "seperate" in the Cloud Firestore database and the source code.
- As stated above, the bleeding edge Cloud Firestore database was a b***h to work with and this applies to renaming a single field in 20+ toilets.
- Took some time writing and testing our renaming script to ensure that it does not nuke those 20+ carefully recorded toilets into oblivion.
- The source code was more straightforward. It just involved a simple replace through multiple files but we had to be sure we didn't miss any instance of the childish typo and thus this took some time too.
- Thus the existence of this shameful commit 0b890ab.
- As we were working on the submission portal, we realised that "panorama" was spelt (quite terribly wrongly) as "paranoma". I blame this on excessive amount of Paranormal Activity I binged on just as summer started.
- How shameful - an entire file name was spelt wrongly:
/src/components/paranoma.js
- For this typo, we were already too jaded by the earlier replacement of the word "separate" and thus we decided to let it be in the source code and database of the webapp.
- The submissions portal was way messier, with half of the references being the correct "panorama", while the other half being the erroneous "paranoma".
- How shameful - an entire file name was spelt wrongly:
- Lessons learnt: encapsulate all hard to spell words into a single function in the future, so spelling errors are easier to fix.
-
Other user testing
- As alluded above, we did let some of our friends try the app so as to hunt for bugs and get feedback.
- In hindsight, we should have tracked all issues with Github issues, but we were young and didn't really git gud with Github back then and thus all we did was haphazardly document issues all around the place.
#fakeNews, we did have many issues with our apps, just that it wasn't documented. ๐
- Just some of the numerous suggestions and ideas we received from our testers/our own testing, all randomly scribbled around my Google Keep. ๐
- Another half of the suggestions/issues (not showing all of course) were on Trello, thus making all this issue tracking thingy very messy.
- Since then, we've appreciated the use of Github issues and have started using them in our submissions portal.
All in all, in milestone 3, we did not work much on the main app. Not much new features were developed and we spent more time on our submissions portal. However, the lessons learnt from the short time working on the main app was definitely valuable and these lessons made writing the submissions portal easier!