End-to-end mobile application development for Inversum
Application development, backend, payment solution integration and design assistance for a company aimed at solving mental problems.
The Inversum application helps solving mental problems. With their digital solution, users can recharge themselves through personalised, guided meditations conveniently at home. By using Inversum's online meditation system, anyone can address the everyday psychological issues of life. Their diverse meditations aid in stress management and reducing anxiety.
Inversum approached our company with the need for developing a mobile application and provided a pre-designed concept created by Cubicfox. Throughout the comprehensive app development process, our company created the backend and integrated the payment solution, while also offering business and IT consultancy.
Firebase - Why do we use it?
We primarily chose Firebase because it allows us to offload the implementation and operation of the application's backend infrastructure to Google. As a platform, Firebase provides various services such as a database, storage, and deployable cloud functions, enabling the realisation of a so-called serverless backend. The serverless architecture means that Google, as a cloud service provider, provides the necessary environment, resources, and scalability for running the entire backend. Since the Inversum application mainly revolves around user management, handling data, and downloading/storing audio files, we didn’t need to build a complex backend infrastructure. The services offered by Firebase were more than sufficient for our - and their - needs.
Furthermore, it is worth mentioning that Firebase can handle the synchronisation of user profiles and meditation audio files. This ensures that the mobile application always operates based on up-to-date data. For example, if there is an error in uploaded meditations, we can easily replace the audio files in Firebase. After the modification, the changes automatically propagate to the client-side as well. Additionally, we needed third-party login functionality (Apple, Google, Facebook), which Firebase also provides. We only had to manually configure it, and once enabled, it seamlessly integrated into the application.
Lastly, Firebase offers the option of manual scaling for an additional fee. This allows us to speed up processes if the application will be used by a large number of users.
Triggers and scheduled functions:
In Firebase, triggers are functions that get automatically called when a specific event occurs, such as the creation of a new document in a Firestore collection, the authentication of a user, or the completion of a Cloud Storage operation. Triggers are defined using Cloud Functions for Firebase, which is a serverless framework for building backend functionality.
Throughout this project, we used triggers to handle in-app purchases through RevenueCat. Whenever a user makes a purchase, RevenueCat automatically creates a new document in our database, and our event handler will be triggered. We’ll explain this in more depth later on.
Scheduled functions, on the other hand, are functions that are executed on a regular basis, such as once per day or once per hour. Scheduled functions are defined to use the same Cloud Functions for Firebase framework, but they are triggered by a scheduled event rather than a specific user action.
Throughout this project, we used scheduled functions to send out renewal reminders to our users. RevenueCat does not send expiration reminders, so we needed to handle this custom functionality ourselves. We created a scheduled function that ran once per day and checked the expiration dates of our user’s subscriptions. If a user’s subscription was about to expire, the system sent them an email and a push notification reminding them to renew their subscription.
Overall, using triggers and scheduled functions in Firebase allowed us to automate a large chunk of the backend processes for our in-app purchases and this way enhance the user experience.
User management - statuses, storing user data, authentication.
The registration and login of users are completely handled by Firebase Authentication, including the integration of third-party login providers. When such a provider-based login occurs, we can see this highlighted in Firebase, and we can retrieve certain data provided by the third-party service, such as the user's name. A fun fact is that Apple allows the hiding of email addresses. If a user chooses this option, we don’t have access to their email address, which can make contacting them or resolving potential issues more challenging.
We also store various metadata about users, such as the statistics of what they listen to, personal preferences, or notifications. We store this data in Firebase Firestore, which is a MongoDB database. It is a NoSQL database that stores information in a document-based format, unlike traditional relational databases. The schema of these documents can be dynamically changed on the fly. During development, we often found the need to add extra fields to the database. Since it is a schemaless database, we could easily modify our database model without the need to regenerate any schema.
The users' data naturally includes the status of their subscription, and we store the parameters, type, and expiration of this subscription in a document attached to each user. To manage our subscriptions, we run scheduled functions in the cloud that check the integrity of user data and the existence of their subscriptions on a daily basis.
We stored the audio files in Firebase Storage, which offers the advantage of easy and secure access to these stored contents by other Firebase services. This is possible as Firebase operates within a closed ecosystem.
Why do we use it and what is it good for? RevenueCat for In app Purchase
RevenueCat is a third-party service that provides in-app purchase functionality for mobile applications. It simplifies the process of integrating in-app purchases for both iOS and Android apps. RevenueCat offers several features, including subscription management, receipt validation, analytics, and more. In our project, we needed to implement in-app purchases for our premium meditations. We decided to use RevenueCat for this purpose, as it offered a robust set of features and an easy-to-use API. With RevenueCat, we were able to offer monthly and yearly access to our premium meditations.
To integrate RevenueCat with Firebase, we used the Firebase extension provided by RevenueCat. This extension made the integration process seamless and straightforward. We simply added the extension to our Firebase project and configured it with our RevenueCat API keys.
RevenueCat integration - Custom event handlers
Once we integrated RevenueCat with Firebase, we needed to handle custom events that were triggered automatically by RevenueCat. We created a custom event handler that maintained the user’s state in our database (judging by the type of the event we need to handle those cases appropriately), as well as in RevenueCat. We also used this event handler to send notifications to our users based on the event type. For example, if a user’s payment was successful, we would send them a message thanking them for their purchase and giving them access to the premium meditations. On the other hand, if there was a billing error, we would send the user a message informing them of the error and asking them to update their payment information.
Overall, integrating RevenueCat with Firebase was a straightforward process, and it allowed us to offer in-app purchases for the premium meditations without the need for complex backend infrastructure. With our custom event handler, we were able to maintain users’ state and keep them engaged with the app through timely and relevant notifications.
Firebase functions were not primarily designed for executing massive logic but rather for performing numerous, smaller operations. Because of this, we had to reconsider the logic for the new payment system since Firebase arrays cannot be directly modified: only fixed elements can be added or removed. As a result, we had to develop a workaround solution during the frontend development process.
Difficulties and obstacles we had to overcome
The core functionality of the app, which is listening to meditations, was easily implemented using Flutter packages. However, the development process was made more challenging by the fact that we initially followed the provided design guidelines for UI development, but both our team and the Inversum team lacked resources for making UI modifications. This led to situations where our engineers had to come up with ideas on how the new, modified UI should look. Additionally, our client wanted to release the application on the App Store and Google Play Store as soon as possible, which resulted in rapid coding and led to errors and misunderstandings. Another difficulty was that our client wanted everything to be displayed pixel-perfect, and they continuously iterated on the required features. This sometimes resulted in requesting a feature to be developed and later realising that it was not actually needed.
To avoid such difficulties, we recommend finalising the functional specifications together before starting the development process. Furthermore, we suggest our clients to rely on our internal design resources with confidence. At ff.next, we have exceptional design professionals who can assist in creating the perfect UX and UI.
Playing meditations
We used modern state management, specifically the BLoC pattern, to implement this functionality, which is completely separated from the UI.
Creating this feature required careful consideration because it's not just playing a single meditation but each session consists of multiple different steps. There could be 20+ audio files to play, which are composed of various Firebase documents with many cross-references to other documents. So, even the structure of each meditation step can be relatively complex. Moreover, they are dynamic, meaning that in some steps, we prompt the user for input with a yes/no tap, which allows for branching meditations. It's even possible to skip several steps and jump ahead based on certain answers.
We also needed various timers. For example, if there's an Internet issue during the meditation, causing an error, we gradually lower the volume towards the end if it's clear that it won't be able to finish after catching up with the buffered part.
We handled the case when the user has no Internet access before starting the playback. In this case, instead of showing the play button, we display a retry button, and no error occurs. We also implemented offline functionality for such scenarios by introducing a download mechanism. If a meditation is already downloaded, the application uses the downloaded audio files by default, even for other meditations that require the same audio file, to reduce data usage. The app synchronises the user's data, including the downloads, every time it launches, ensuring that the downloaded data is always up-to-date. As per the specific request from the Inversum team, the downloaded data is refreshed even if the structure of the meditation hasn't changed, and only the audio file has been replaced with the same name.
It's worth noting that metadata for specific files can be retrieved from Firebase, allowing us to design it in a way that it only downloads something if it has actually changed, so the app doesn't re-download entire meditations every time it starts. Furthermore, the synchronisation process also removes downloaded audio files that are no longer needed at the end, optimising the phone's storage usage.
Both the synchronisation and the downloads run separately from the UI, enabling users to use the app while these processes are running.
Authentication
The app utilises multiple authentication methods, all handled through Firebase Authentication.
Understanding the various authentication options was challenging due to their complexity. In the case of email registration, users have 24 hours to verify their accounts; otherwise, their accounts are deleted. Firebase Authentication handles interoperability. If a user attempts to sign in with a provider (e.g., Google) using the same email address they used for a previous registration, and the email address has been verified, we add the provider-based login option. This means that users can log in using both email and Google. However, if the email address has not been verified, the Google login overrides the email login, and users cannot log in with email anymore.
Implementing email authentication was straightforward and easily accomplished. Integrating Google authentication was also relatively simple, thanks to the availability of a Flutter package, although it required additional configuration in Firebase.
On the other hand, integrating Facebook authentication posed some challenges. It involved configuring the developer Facebook account, which was owned by the team at Inversum. We had to request access to the account for this purpose. The integration of Facebook authentication required several configuration steps, and for testing, we had to create test users in the developer account.
The integration of Apple authentication proved to be particularly difficult and involved numerous configuration steps. These configurations had to be performed in the Apple Developer account. Initially, we set them up in our own account, but later we had to replicate them in our client's account as well. The iOS part was successfully implemented, but for Apple Sign-In on Android, we had to create a separate custom server, which we later managed to connect with Firebase.
Dynamic links
The app uses dynamic links in multiple instances. The purpose of dynamic links is that when such a link is opened on a device, and it is associated with an application, the app is launched if it is installed on the device. If the app is not installed, the user is redirected to the respective store (App Store or Google Play) to the app's page.
We employed Firebase's dynamic linking functionality in several parts of the application. After email registration, we sent a confirmation email to the registered email address. The user has 24 hours to confirm their account by clicking on the link provided in the email. Thanks to the dynamic link, clicking on the link takes the user back to the app. We also utilised dynamic links in the case of password changes.
For internal testing purposes, we utilised Firebase App Distribution. We created three environments (flavors) and released separate app versions for each of them for testing. These environments were mock, staging, and production.
To expedite the release processes, we implemented Fastlane, which automates the build and upload process, eliminating the need for manual building and uploading to Firebase for each new version. This code ran on our internal server.
Unfortunately, we encountered several issues with this approach, primarily due to limited storage capacity, and our internal system took a long time to execute the code (sometimes up to an hour). Most of the time, issues were only discovered at the end, leading to a failed process.
The Android components generally ran without errors, but the iOS components often resulted in failures. For external testing, we used the Internal Track on Android and TestFlight on iOS. The reason for the failures was a lack of DevOps expertise, as we currently do not have a DevOps specialist on our team. However, our developers delved into this area during the project and acquired valuable experience, enabling them to build a stable pipeline smoothly in future projects.
The integration of various authentication methods, including email, Google, Facebook, and Apple, presented its own set of challenges, but we effectively configured Firebase Authentication to handle user registrations and logins. Dynamic links played a significant role in user confirmation, password changes, and directing users to the app's page when clicked. The release process was optimised using Firebase App Distribution for internal testing and Fastlane for automated build and upload processes. Though we faced some issues during the release, we gained valuable DevOps expertise and set the stage for a stable pipeline in future projects.
In conclusion, our collaboration with Inversum resulted in the successful development of a mobile application that empowers users to release stress and decrease anxiety. Despite the difficulties we encountered, such as UI modifications, rapid coding, and evolving feature requirements, we successfully navigated the development process. Overcoming challenges through effective communication, adaptation, and innovation, we delivered a high-quality product and we really hope that we could truly support the mental health of people in today’s high-stress world.
More work
CASE STUDY
OTP
UX research, UX consultancy & youth banking strategy for the bank regarding an app targeting teenagers with non-banking functions.