Why Flutter's One-Way UI Update Closes a Bug Class That Trips Up AI Coders
From a Short-Drama App Bug: The Advantages of Reactive Programming in the Age of AI
Hello everyone, I'm Lao Liu
Recently I saw news that Hongguo (a short-drama app) has become the fourth largest app. Short dramas are indeed a time sink, and also one of the few female-oriented contents I can watch.
But I have a habit when watching short dramas: when I come across an uninteresting segment, I long-press the edge of the screen to fast-forward. A few days ago, I triggered a small bug.
This bug isn't complicated, but it's very suitable for discussing one question. Why are some bugs easy for both humans and AI to make? And what kind of trouble can reactive frameworks save us from?
A 2x Speed Bug
The reproduction process is simple.
Long-press the edge of the screen to enable temporary 2x playback. When the current episode finishes and the app auto-advances to the next episode, then release your finger.
The actual playback speed has returned to normal 1x, but the speed button in the bottom-right corner still shows 2x.
Tap to open the speed selection list, and the display inside is normal. The same playback speed gives two different answers in two different interfaces.
What does this bug have to do with reactive programming?
The connection is that it's a typical synchronization problem in imperative UI development.
Solution 1: Missing a Sync Point When Adding a New Feature
First, let me clarify: the following implementations are guesses based on the observed behavior. I haven't seen the actual code, nor am I sure which approach it really uses.
But this guessing process is enough to help us understand how the problem arises.
One possible flow is like this.
Long-press the edge of the screen to enable temporary 2x playback.
Replace the floating info of the normal playback interface with the floating info of the temporary fast-speed playback.
Because the entire floating display is replaced, the original floating UI doesn't need to be modified and keeps its 1x display.
When the current episode finishes, it auto-jumps to the next episode. To buffer ahead, a new playback window is likely created.
Since the user hasn't released the finger, the screen still shows the temporary fast-speed floating info.
The new window is created at 2x speed, so its normal floating info is also 2x.
The user releases the finger, playback speed returns to normal.
The code closes the temporary fast-speed floating UI and switches back to the normal floating UI, but forgets to update the speed button text in the normal UI.
The problem lies in the last two steps.
Long-press edge fast-forward is likely a feature added after the playback page was built. When developing this feature, if the main attention was on long-press, release, and the floating layer switch, it's plausible that the scenario of auto-advancing to the next episode was not considered.
Imperative UI allows developers to grab a widget and directly modify its text and state. That's flexible, but the cost is that for every new scenario, you have to manually check all related sync points.
The state has changed, but the UI hasn't followed, and the bug appears.
Solution 2: Unified Notification with Observer Pattern
Within the imperative programming system, there is also a mature improvement for this problem: the observer pattern is a classic one.
The player holds the playback speed data as the publisher. All info display interfaces floating above the player act as observers of this data.
Whether it's the temporary fast-speed interface or the normal playback interface, as long as they receive a speed-change notification, they update their display.
Logically, as long as everyone strictly follows this rule, the problem is solved. The underlying idea behind reactive UI frameworks is also this state-notification mechanism.
So why can this approach still produce bugs?
Because the rules need to be maintained by people, and the framework still retains a path to bypass the rules and directly modify the UI.
Suppose the project used the observer pattern early on, and later a new colleague or someone transferred from another department is assigned to develop the long-press fast-forward feature.
They might not understand the past development conventions, or they might not even see the relevant documentation.
Even if they knew the past conventions, they might think: temporary fast-forward is just a standalone floating layer, it doesn't affect the original interface, so there's no need to modify the original UI logic; just directly change the playback speed in the interface.
Each judgment alone seems reasonable, but in the whole playback flow, one sync point is missed.
So, conventions written into documentation depend on everyone remembering them. Constraints built into a framework keep playing their role in every development.
Solution 3: How Reactive Frameworks Do It
Take Flutter as an example. Flutter's way to solve this hidden danger is very simple: it doesn't provide you with any method to directly modify a UI element.
That is, you cannot call a set method on a UI element to update that element.
The only proper way to update a UI element in Flutter is through state management.
In Flutter's philosophy, all mutable data is state.
And UI element updates can only be bound to and listen to state changes.
The only way for developers to update UI elements is to provide a callback to the framework, which generates the entire UI based on the latest state when the state changes.
In other words, Flutter turns the observer pattern into the only path to update UI. Not just Flutter; the underlying approach of all reactive development frameworks is similar.
The result is that all mutable elements on the UI must listen to corresponding state changes. As long as the data state changes, all related UI elements are notified and updated.
This fundamentally eliminates the kind of problem mentioned above.
If speed state is 1x, the UI is generated according to 1x. If speed state becomes 2x, the related UI is regenerated according to 2x.
If the temporary floating layer and the normal floating layer read the same state, then when you release your finger and just change the state back to 1x, the related UI will update according to their declarations.
Of course, Flutter doesn't automatically eliminate all such bugs. Developers might still design states incorrectly, or have two pages read different data sources.
What it reduces is a specific class of problem: the state has been modified, but the developer forgot to manually modify a certain widget.
The value of reactive frameworks is maintaining fewer sync relationships that rely on human memory.
Where Humans Easily Forget to Update, AI Does Too
At this point, some might ask: what does this have to do with AI programming?
Because the mistakes humans make in imperative frameworks, AI is just as likely to make.
Lao Liu has an open-source project where all the code is written by AI. I only confirm what features are needed, and don't modify the code myself.
https://github.com/lzt-code/ai-gateway-desk
Recently, this project also had a similar sync problem.
I added a new Provider on the AI provider's Provider page, but the model list page didn't reflect this new addition.
The root cause is also that in an imperative UI framework, one related modification was missed.
In my workflow, I typically use Claude 5.5 Opus or Kimi K3 for solution design, and DeepSeek V4.1 Flash to actually modify the code.
Claude 5.5 Opus didn't think of needing to sync the model list page when adding/removing Providers. DeepSeek V4.1 Flash didn't spot this omission when writing code.
But in my Flutter projects, using the same models and agents, I've never encountered a similar problem.
This sample isn't large, so we can't conclude that Flutter projects won't have such bugs. But this comparison is enough to remind me that framework constraints work for AI as well.
When AI generates code, it is also influenced by context. If the requirement doesn't explicitly mention a page, it may not proactively check it. If there are multiple UI update paths in the project, it may only modify the one it sees currently.
Where humans easily miss, AI may also miss. The faster code generation is, the faster omissions get written into the project.
In the AI Era, Old Frameworks Have a New Layer of Value
This article doesn't intend to promote Flutter or mythologize AI programming.
Lao Liu just wants to use scenarios from watching short dramas and actual development to share a feeling that's becoming clearer.
Frameworks that have stood the test of time, besides helping humans write more stable code, also help AI reduce some low-level omissions.
In the AI era, framework constraints are becoming part of quality management. If you leave the correct update paths clear and narrow the space for arbitrary detours, both human developers and AI can avoid more pitfalls.
Moreover, leaving these open paths makes it convenient for humans to write code, but for AI that convenience has zero benefit.
Have you ever encountered this kind of bug in AI programming where "one place is changed but another page doesn't follow"? Feel free to comment.
🤝 If you're interested in client-side or Flutter development, feel free to contact Lao Liu, let's learn from each other.
🎁 Private message to get Lao Liu's free "Flutter Development Handbook", covering 90% of app development scenarios. Can be used as a knowledge map for Flutter learning.
💬: laoliu_dev
📂 Lao Liu also organizes his historical articles in a GitHub repository for easy reference.