Practice Hub Rework & Android Update
This update touches every layer of FretsNotes — the server, the iOS app, and the Android app. The headline features are a reworked practice hub and a batch of Android fixes, but the underlying change is bigger: more of the app’s behavior is now controlled from the server, which means we can tune things without shipping new app versions.
Configurable Practice Unlocks
Previously, exercise modes in the practice hub unlocked only when you passed a course gate that included that exercise type. Complete a gate with “Hear & Name”? That mode appears in free practice. It was a clean design, but inflexible — if we wanted to give beta testers access to all exercises, we had to ship a code change.
Now there’s a server-side feature flag called practice_unlock_all. When enabled for a role tier, every exercise mode is available in free practice regardless of course progress. The server decides who gets it based on their account tier, and the change takes effect on both platforms instantly through the existing sync pipeline.
The same treatment went to Quick Start templates. The client used to check both a feature flag and a hardcoded role requirement. That redundancy meant the server couldn’t actually control access on its own. We removed the client-side role check on both iOS and Android — now the server flag is the single source of truth.
Android Curriculum Switching
The curriculum picker on Android looked right but didn’t actually work. You could tap a different curriculum, see the icon color change, and… the same courses would appear. Three separate issues:
Missing curriculum data. The curriculum list was loaded once at startup, but never refreshed when the user’s role changed or after login. We added a role-change observer to the curriculum store and moved the initial load to trigger after authentication, matching the iOS behavior.
Blocking network calls on the main thread. Pull-to-refresh would trigger server sync, but the HTTP client uses synchronous OkHttp calls. Running those on the main thread froze the UI, so the refresh spinner never dismissed. We wrapped the network operations in a background dispatcher.
Series order mismatch. This was the subtle one. The server’s curriculum manifests define series names for future content (“Foundations”, “Ear Training I”), but the bundled Android courses still use the original names (“Essential”, “Position II”). Using the server’s series order directly caused courses to appear in the wrong order — or not at all. We added a validation step: if the curriculum’s series order matches the loaded courses, use it as a filter; otherwise, fall back to the built-in order with role-based filtering. This means the “Introduction” curriculum correctly shows only the Welcome course, while the main curriculum displays all four series in the right order.
Progress Display Fix
The curriculum picker showed “1%” progress on a completed course. The calculation was dividing by all 57 bundled courses instead of just the ones belonging to the active curriculum. Now each curriculum card computes progress only over its own series.
What’s Next
These changes move us closer to full server-driven configuration. The pattern is the same every time: define a flag on the server, deliver it through the sync pipeline, and let both clients react. No app update required. We’ll keep expanding this to more features over the coming weeks.