跪拜 Guibai
← All articles
Frontend

From 2.7.0 to 3.0.0: The Flutter Rewrite That Started by Deleting Everything

By 北岛贰 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A full rewrite on this scale is risky, yet the result is a working app with 170 tests after seven days, proving that AI can accelerate a developer who already knows their architecture. The debt-to-rewrite decision and the streaming audio design are directly applicable to Flutter apps with similar problems.

Summary

Bob Music 3.0.0 came out of a seven-day rewrite that started with deleting the entire lib/ directory. The previous 2.x codebase had accumulated six categories of debt: whole songs kept in memory, progress polled at 300ms, lyric timing off by a factor of ten, race conditions on startup, SQL table name concatenation, and a state-management mess that triggered full-tree rebuilds. The author, who had learned Flutter while building 1.0, decided that incremental refactoring was pointless because there were zero tests and the foundation was crooked.

The rewrite kept only the Bilibili SDK, replacing state management with Riverpod 3 and its codegen for precise invalidation, banning event_bus entirely. The database shrank to four tables with parameterized SQL, and a new proxy audio source streams from CDN via range requests, caches with LRU up to 2GB, and persists playback state across restarts. Local songs get a content fingerprint based on file size, duration, and normalized title/artist so likes and lyrics survive rescans. The app now pulls from five sources: Bilibili, Kuwo, NetEase, QQ, and Qianqian, with a three-level lyric lookup.

AI played a central role, but only after the developer wrote architecture constraints and documented module pitfalls. AI generated code under those rules, while the human handled design decisions, code review, and real-device verification. The build environment was the dirtiest part: 132 dependencies needed major upgrades, AGP 9, Gradle 9.3.1, JDK 17, forced NDK version, and a myriad of timeouts. The final release has 170+ tests, including fake implementations for all five sources and matrix tests for playback strategies.

Takeaways
— 2.7.0 kept entire songs in memory and polled progress at 300ms, with lyric timing off by a factor of ten.
— The rewrite deleted all lib/ code except the Bilibili SDK, which was the only asset worth keeping.
— State management moved from provider + event_bus to Riverpod 3 with codegen; event_bus was completely banned.
— Database now uses four fully parameterized SQL tables, and only the Repository layer touches SQL.
— Local songs get a content fingerprint from file size, duration, and normalized title/artist to preserve likes and lyrics across rescans.
— New audio engine uses range requests to CDN, streams and caches with LRU up to 2GB, has a watchdog fallback, and persists playback state.
— Five music sources: Bilibili, Kuwo, NetEase, QQ, and Qianqian, with a three-level lyric search chain.
— 170+ tests include fake implementations for all five sources and matrix tests for playback strategy/state machine timing.
— 108 commits were made in seven days from deleting lib/ to shipping 3.0.0.
— Environment upgrade required 132 major dependency upgrades, AGP 9, Gradle 9.3.1, JDK 17, forced NDK 28.2, and various timeouts.
— AI generated code under documented rules, but real-device verification was still mandatory for the playback core.
Conclusions

The decision to abandon a half-written refactor plan because the foundation was crooked and tests were zero is a judgment call that many teams face; this rewrite succeeded in a week, but the app's small size and single-maintainer nature likely made it feasible.

AI's role was constrained to generating code under explicit rules, and the author still had to verify on real devices; the silent failure from a late-initialized Timer swallowed by a catch shows why AI output can't be trusted blindly.

The environment upgrade (AGP 9, Gradle 9.3.1, JDK 17) consumed a significant portion of the week, highlighting that toolchain maintenance is a hidden cost of any rewrite.

Choosing not to migrate old data avoids bringing old bugs into a new implementation, a pragmatic trade-off for a personal app but a tougher call for products with many users.

Concepts & terms
Content fingerprint
A unique identifier for a local song computed from file size, duration, and normalized title/artist, used as the primary key so likes and lyrics survive filesystem rescans.
Proxy audio source
A custom playback component that issues HTTP range requests to a CDN, streams while downloading, caches to disk with LRU eviction, and falls back to backup links via a watchdog timer.
Watchdog timer
A mechanism that intervenes if no data arrives within 15 seconds, triggering fallback or recovery to avoid a silent stall.
Riverpod codegen
A Riverpod feature that generates provider definitions from annotations, enabling compile-time validation and precise invalidation of only the providers affected by a state change.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗