SJ
Memoir
Deep Breathing & Mindfulness on iOS and Android
"A language-independent meditation app to help ease the world's anxiety"
My Role:
Lead Developer / Cofounder
Team Size: Two
Myself + 1 Product Designer
User Testing:
5.9 Million views on Tiktok
158,000 followers
Technologies Used:
- React Native
- Expo
- JavaScript
- CSS
- Figma
- Google Firebase
- Apple Developer Program
- Sign In With Facebook
TikTok Marketing
Memoir Features
- Memoir promotes mindfulness and serenity through deep breathing and meditation exercises.
- Users can sign in with their Facebook, Apple ID, or email to save their favorite exercises and track their personal exercise progress.
- Users have a clean and simple-to-use UI to play and pause exercise videos, set timers, and save favorites.
- Personalized user progress tracking add to the user experience and help to build better habits.
User Journey
Guiding Customers to Create an Account
We delayed the account creation process to allow the customer to explore the app before committing to signing up.
We designed the app to allow customers to be able to try out the free exercises as they please, and would be prompted to create an account when visiting the favorites or profile pages.
This kept an incentive for the customer to create an account while reducing the chance of them leaving the app due to an early account creation request.
Challenges Faced:
Caching the Downloaded Image and Video Assets from Firebase Storage using Expo FileSystem API
The following code gist was shrunken from the original code for simplification. The original uses a regular expression checker to categorize assets as "images" or "videos".
- Firstly, the specifically requested
image
andvideoFile
assets are passed down as props to the individualExercise
components. useState()
hooks are then initialized to set the actual image and videos to be rendered for later on. TheuseEffect()
hook will run the initial caching function on mount.cacheAsset
is anasync
function, that first checks if the file already exists within the cache to prevent unnecessary download requests, saving bandwidth.- If it wasn't cached, the download URL of the file is retrieved from Firebase Storage. The URL is used to download the file to the device's cache using the Expo FileSystem API. The
useState()
states are then stored using the newly downloaded assets. - Finally, with the stored states, the
cachedImage
is used to render the exercise image. When you press on that image, it navigates to theExerciseVideo
screen along with thecachedVideo
passed down as aroute.param
.
UI: User Stats Scroll-Up Numbers Animation
How I implemented the custom animation for the numbers scrolling up:
- I first made a function to render a short vertical bar of numbers by dividing each specific stat number by 5.
- Then, I used a
MaskedView
component from Expo to create a mask for the bar to hide behind. - Whenever this specific screen was focused by the user, a
setTimeout
triggers the transform animation usingAnimatable
. - With wild cases such as integers ranging from 0 to long-digit integers like 1,000,000 or beyond, I implemented a custom width solution using conditionals for responsive widths.
Dynamically Programmed “Moving Target” Goals & Congratulation Alerts
An alert dialog pops up whenever the user hits their "moving target" goals, which would always increase as the current benchmarks were met.
- The practice time goals increase from a 30 minute goal, to a 2 hour goal, to a 5 hour goal, and then increments by 5 hours indefinitely.
- Those conditional rules were stored in a variable and used to trigger the alerts whenever the goals were met and the user visited the stats page.
- The "dismissed" state of the alert begins as ‘false‘ and is then set to ‘true‘ when the user dismisses it.
Used Google Firebase‘s Realtime Database to Store Personalized Favorites and Progress Objects
Users can keep track of their exercise goals and progress such as hours completed, sessions completed, and best streaks achieved.
- The number of seconds a user spends on any timed exercises are incremented, which are then converted to minutes and hours as the user progresses.
- The user‘s "Sessions Completed" tracker increments whenever a user finishes a timed exercise.
- The user‘s "Current Streak" and "Best Streak" are incremented whenever they start any exercise on the next calendar day. This is determined by comparing the last tracked date with a current
new Date()
Object.
UI/UX Solution: Horizontal ScrollViews used for Custom Option Selectors
In order to set timers for breathing exercises, I implemented a horizontal options selector to choose timed intervals. My function renders an array of TouchableOpacity
‘s onto the screen and is hidden/revealed when the user presses the timer icon. Selecting a time option starts the timer and hides the options automatically.
Another horizontal ScrollView was used to set the timer for the Meditation Timer Screen.
I created a custom "scroll wheel" by gathering the measurements for the contentOffset.x
, the contentSize.width
, and the layoutMeasurement.width
and calculating the percentage of space used within the entire ScrollView
.
This homemade "scroll wheel" includes haptic feedback to ensure the user is interacting with the adjuster.