Moodify - Student Engineering Journal

Moodify began as an iOS prototype while I was taking a Special Topics computer science class in early 2025. Later, I rebuilt it as a larger personal iPhone app. The early version asked a simple question: could a mood choice lead to a useful song from the user's own music library? The newer version kept playback inside the app and became more focused on queue behavior, personalization, and explaining why a recommendation changed.

The question I kept coming back to was: how much can an app learn from messy listening behavior without pretending it knows exactly how someone feels?

About the early dates

I looked back through an older Xcode archive, source-code headers, file dates, and dated screenshots to organize the 2025 entries. I cross-checked them with each other and only included files and screenshots that clearly matched the work from that time, so the timeline is as accurate as possible.

The 2026 entries come from the newer project's git history, and I grouped related commits when they were part of the same problem.

DateAreaEngineering journal
2025-01-24Initial structure and data modelI created the first model, controller, and view-model files, plus a Core Data MoodEntry and a history view model. I wanted Spotify, mood history, and interface code to stay separate, and I decided that each mood entry should persist after the app closed.
2025-02-01Navigation and history fetchingI added the first tab-navigation files and made mood history load newest-first from Core Data. I also created the default Xcode test targets. The main problem was getting saved data onto the correct managed-object context and then exposing it so SwiftUI could update.
2025-02-10 to 2025-02-12Mood selection, HealthKit experiment, and Spotify stateI started the home screen, built a HealthKit experiment for mindful-session data, and created a Spotify player view model that watched playback state, song information, and album artwork. Both services depended on permissions and asynchronous callbacks, so I had to be careful about when the UI changed.
2025-02-23 to 2025-02-25Spotify login and permissionsI built Spotify response models and worked through the login flow. I requested the scopes needed for playback and library data, and I added logout behavior that removed saved tokens so an old session would not hide a login bug.
2025-02-26 to 2025-02-28SwiftUI session-manager ownershipI hit compiler errors while passing the Spotify session manager into the root SwiftUI view. The SDK object was not itself a SwiftUI view model, so I changed the design so one observable wrapper owned the Spotify objects and was passed through the SwiftUI environment.
2025-03-03App capabilities and HealthKit setupI added the HealthKit entitlement and privacy-purpose strings. Writing the integration code was not enough; the target capabilities and permission descriptions also had to be correct.
2025-03-11 to 2025-03-18Persistence and Spotify servicesI expanded MoodEntry to save the emoji, mood level, genre, track, artist, Spotify URI, cover art, playlist, timestamp, and HealthKit-sync state. I also built Spotify profile, playlist, track, token, pagination, and playback-control code.
2025-05-27 to 2025-05-30Custom mood mappingsI replaced permanently hard-coded mood mappings with a MoodMapping model and editing UI. I stored the nine small settings as encoded JSON in AppStorage, while keeping the growing mood history in Core Data.
2025-06-01Shared app compositionI created one shared Spotify session wrapper, one shared data manager, and one Core Data context at the app level, then injected them into the Home, Entries, and Settings tabs. This kept login state and network work consistent across screens.
2025-06-04Spotify redirect and Now PlayingI registered the moodify:// URL scheme, handled the return from Spotify login, and built a Now Playing screen with artwork, song details, duration, and a Play on Spotify action. I also added a fallback so the screen would not fail when a matching track was unavailable during development.
2025-06-05Entry management and history chartsI refined the emoji grid, Settings, saved-entry list, and history dashboard. I grouped entries by calendar day and by emoji, then converted those groups into small models for Swift Charts.
2025-06-06Dynamic mood mappingsI finished a screen where each of nine moods could be mapped to a genre from the user's Spotify data. Because the screen could load before the network request finished, I showed fallback genres first and replaced them when the live list became available.
2025-06-08Concurrent genre collectionI collected genres for every artist on a track using a DispatchGroup, combined them in a Set, and protected the shared set with a serial queue because several callbacks could finish at once.
2025-06-11End-to-end V1 workflowI connected mood selection to genre lookup, track filtering, fallback selection, Core Data saving, and the Now Playing sheet. The school-project version could take a mood, choose a Spotify-library recommendation, save the mood/music pairing, and show history and statistics.
2025-06 to 2026-03Gap before the newer versionThe saved development history has a gap here. The next major direction change appears in March 2026, when MusicKit made it possible to keep Apple Music playback inside Moodify.
2026-03-15V2 rebuild with MusicKitI started a newer version so mood choice, recommendation, playback, and feedback could stay inside one app instead of handing the user off to Spotify.
2026-03-21Apple Music onlyI removed Spotify from the newer version and made Apple Music the only provider. Supporting two providers no longer made sense when only one supported the integrated playback loop I was building.
2026-04-21Beta analytics and listening signalsI added a small Mixpanel analytics service and began tracking session starts, skips with listening position, song completions, and explicit likes or dislikes. I wanted a way to study patterns across real listening sessions instead of depending only on what testers remembered to tell me. These actions still did not have one automatic meaning. A quick skip could suggest something different from a skip near the end, so I treated the analytics as evidence to investigate rather than a simple good-or-bad score.
2026-04-22 to 2026-05-02Expanding the TestFlight betaI uploaded the first internal TestFlight build on April 22 for family and engineering testers. Build 115, uploaded on May 2, was also made available to an external Friends group. Moving beyond testing on my own device gave me real listening sessions and behavioral signals to inspect. Watching my mom use the beta, I noticed that even though she wanted music to fit her mood, she still browsed a lot before finding what felt right. That made me question whether the app should always make someone name a mood before listening. I also learned to separate group access from actual use, so I looked at installs, sessions, feedback, and Mixpanel events instead of treating the number of invited testers as engagement.
2026-05-05 to 2026-05-11Session navigation and Now Playing stateI fixed previous/next oscillation, swipe cards disappearing, artwork identity mistakes, snap-back flashes, and layout problems. I first treated many of them as animation bugs, but they kept pointing back to unclear boundaries between browsing, confirmed playback, and cleanup timing.
2026-05-16 to 2026-05-19Queue backfill and artwork identityI started Apple queue backfill earlier, showed progress, prevented some navigation reactions to temporary pauses, and froze outgoing artwork while the real player settled. Queue timing changed what the user saw, so it could not stay hidden as an internal detail.
2026-05-24Mood mappings and recommendation testsI added multiple genres per mood and tests for repeated songs and artists. One genre per mood felt too narrow, and a queue could feel broken even when it technically returned music.
2026-05-25Media ownership and lifecycleI kept media ownership until the session explicitly ended and tightened background, lock-screen, and remote-control behavior. I learned that iOS media behavior depends on which part of the app owns the session, not only whether the play button works.
2026-05-26Safer refactoringI added boundary checks, behavior tests, one clearer path for playback commands, and smaller testable pieces for queue and recommendation logic. I wanted risky changes to be guided by tests instead of memory.
2026-05-27Playback truth and learning signalsI made the session model follow what Apple Music was actually playing, cleaned up skip/completion/listen-depth processing, and made logs easier to search.
2026-05-31 to 2026-06-07Data cleanup and remote playbackI guarded duplicate mood ordering, separated starting mood definitions from learned profiles, and added AirPods and Siri playback support that still needs real-device testing.
2026-06-28 to 2026-07-05Mood Vector and inspectable personalizationI added an experimental song mood vector, personal adjustments, inspection views, recommendation explanations, and a newer CloudKit record structure. I treated the vector as one scoring signal, not proof that the app understood emotion.
2026-07-06 to 2026-07-07Queue controls and recommendation diagnosticsI blocked controls during unstable queue boundaries, handled pending advances after backfill, treated equivalent playable IDs as the same track when needed, and added recommendation outcome exports. I wanted to inspect what the recommender did instead of judging it only by whether one song felt right.
2026-07-19Product measurementI rebuilt the Mixpanel dashboard around three questions: does Moodify find a fitting song quickly, learn from repeated mood sessions, and bring listeners back? I also added an engagement funnel for app use, session depth, and return frequency. The new analytics are live, but more data and a few tracking improvements are still needed before drawing conclusions.
2026-07-24 to 2026-07-28Comparing repeated mood sessionsI added a same-mood session counter so I could compare a first, second, and later session instead of mixing them together. I also improved playback and personalization reliability and saved more context behind the experimental emotional-index chart so older points could be rebuilt and grouped more clearly. The goal was to make changes easier to inspect, not to claim the chart directly measures emotion.
2026-08-02Starting music without forcing a mood labelI came back to what I had noticed while my mom was beta testing: sometimes listening helps someone figure out what fits instead of the other way around. I added a “Just Play” option so the app can start from a private learned guess without forcing a mood label first. It keeps that guess separate from explicit mood history so it does not silently turn an inference into a fact. I also added a dedicated mood-grid editor and one clearer status flow for preparing, playing, refilling the queue, or ending a mix. This made the app more flexible without weakening the meaning of user-chosen moods.
2026-08-02 to 2026-08-03Mood cloud home and in-session tuningI replaced the old stacked home dashboard with a rotatable mood cloud so choosing music feels less like filling out a form. Recent listening patterns can change which mood is emphasized, but the user still decides what to play. I also moved skip-based refinement into a small “Tune this mix” sheet and tightened session and playback state so an active mix can adjust more reliably. I added reduced-motion and accessibility behavior, but I still need more real-device and tester feedback on the new interaction.
2026-08-04Safer personalization updatesI tightened what happens after someone tunes a mix so one refinement is handled as a single transaction. Moodify now keeps the previous state and can roll back if a write fails instead of leaving the session partly updated. I also reduced duplicate analytics and CloudKit writes, batched record updates, and split some large preference and view-model code so the interface does less work during playback. The new tests cover successful updates and rollback paths, but I still need more real-device sessions to confirm the performance and sync changes under normal use.
2026-08-05Keeping automatic guesses from teaching themselves too stronglyMagic Wand can begin with a mood that Moodify inferred, but I did not want the app's own guess to become as strong as a mood someone chose directly. I separated explicit, inferred, and unattributed learning, capped inferred evidence at half the strength of explicit evidence and then scaled it by confidence, and made the explanation screen say when a mix began from a hypothesis. I also added tests comparing explicit and inferred learning. This lets automatic sessions learn cautiously without turning Moodify's guess into a fact.
2026-08-07 to 2026-08-08Making listening evidence easier to trust and inspectI fixed two measurement problems before relying on the new charts. A skip or completion can arrive after Apple Music has already moved to the next track, so I kept the outgoing recommendation impression long enough to attach the outcome to the right song. I also started separating same-day, 1–7 day, and later returns instead of treating every repeat session the same. Then I moved mood-match, emotional-index, and recommendation-outcome views into a dedicated Mood Insights screen instead of leaving several analytics cards on Home. The tests cover attribution and saved return windows, but I still need real beta data before saying the recommender or retention improved.
2026-08-08Smoother artwork transitions and bounded image cachesI found that Now Playing could replace artwork while a swipe was already leaving the screen, which made the transition hitch even when playback was correct. I changed the outgoing card to use only captured or same-track artwork, lined up the committed artwork reveal with the swipe timing, and added count and memory limits to decoded artwork caches. I added tests for the transition timing and cache-cost calculation, but I still need longer real-device sessions to see how much this helps under normal use.
2026-08-09Optional Apple Health State of Mind loggingI brought an Apple Health idea from the early Moodify prototype into the current app using State of Mind. Moodify can save a reflection only when I explicitly choose or confirm a mood; inferred or Just Play moods are not written to Health. I kept the mood-to-Health mapping conservative and inspectable, added permission, opt-in, failure, and duplicate-save handling, and validated the write flow on a real device. The goal is to make reflection easier without turning Moodify's own guesses into health facts.
2026-08-10Keeping a reset from coming back after cloud syncI found that clearing a learned mood profile on one device could be undone when an older cloud copy merged back in. I added timestamped reset markers so an intentional reset wins over older learning, while genuinely newer learning can retire the marker. I updated the newer cloud-sync format and added tests for reset-only snapshots and local/cloud merge cases. This is a reliability fix, not a claim that the personalization itself got better.

Looking back

The 2025 version taught me how much work sits behind a simple interaction. Choosing an emoji and receiving music required OAuth redirects, permissions, asynchronous networking, pagination, shared SwiftUI state, persistence, and fallbacks.

The newer version changed the problem. Once playback stayed inside Moodify, most of the difficult work became state, timing, and interpretation. A lot of progress came from trying a direct fix, finding another edge case, adding logs or tests, and then making the structure clearer. Family and friends are now testing the app on their own devices, but I still need more coverage for edge cases around Apple Music, AirPods, Siri, and CloudKit.