Skip to content

State persistence/serialization support#14

Open
mitchcapper wants to merge 1 commit intoGeorgeEnglezos:mainfrom
mitchcapper:state_saving
Open

State persistence/serialization support#14
mitchcapper wants to merge 1 commit intoGeorgeEnglezos:mainfrom
mitchcapper:state_saving

Conversation

@mitchcapper
Copy link

Depending on if/when this is merged it will generate merge conflicts with my others for sure. I will resolve in whatever order merged.

This persists the users last state back into the app on start. This also 'fixes' the issue where switching tabs reset all the options selected.

I say 'fixes' as the code specifically seemed to call commandService.resetToDefaults() which would seem to imply I meant to reset any time you switched tabs?

This is largely handed off to AI for code generation and I can't say I am fluent enough with flutter coding to see if there are any pitfalls with this approach.

Overall, while this changes a lot of files, it is a significant reduction in line counts (+914 -1366 net change -452 lines) and removes many places where options were duplicated. While I am always a fan of write once this was done at the same time as this serialization PR because the serialization was going to result in much more boilerplate code otherwise.

I had been working on a DEVELOPERS.md file it is included here due to the notes about the new freeze/serialization requirements before building.

Overview

  • State persistence: All 10 scrcpy option panels now serialize to JSON and auto-save to %APPDATA%/ScrcpyGui/scrcpy_options_state.json via a 4-second debounced timer, restoring on app startup.
  • Immutable option models: Every option class (AudioOptions, GeneralCastOptions, etc.) converted to immutable frozen value objects (final fields, const constructors, copyWith) with @JsonSerializable() code-gen, wrapped in a top-level OptionsBundle. This was required to be able to automatically serialize the members and handle when they were missing in the json.
  • Granular panel rebuilds: All 11 UI panels migrated from Consumer<CommandBuilderService> (rebuilds everything) to context.select (rebuilds only when the specific options object changes), significantly reducing unnecessary widget rebuilds.
  • Centralized app paths: Duplicated getApplicationSupportDirectory() + directory-creation logic in SettingsService, CommandsService, and the new OptionsStateService replaced with a single cached AppPaths.getBasePath() utility. The getApplicationSupportDirectory() looks like it should result in the exact same directory for windows/macos that we were manually computing before.
  • Bug fix: Removed the resetToDefaults() call on tab switch that was wiping user configuration;

Notes

  • Code-gen required: After pulling, run dart run build_runner build --delete-conflicting-outputs to regenerate scrcpy_options.g.dart. The app will not compile without it.
  • Save file format change: Existing users have no prior state file, so first launch simply starts fresh — no migration needed. However, if the option model fields are renamed in the future, the saved JSON may fail to deserialize (gracefully falls back to defaults via try/catch).
  • 4-second debounce: Rapid option changes within 4 seconds are coalesced into a single write. If the app is force-killed (not closed normally), the last ≤4 seconds of changes may be lost. Normal window close calls flushPendingSave() to mitigate this.
  • Recording state intentionally not persisted across restarts: loadOptionsFromJson clears ScreenRecordingOptions because the output filename contains a session-specific timestamp that would be stale.
  • window_manager dependency: The onWindowClose hook relies on WindowListener from window_manager (already a project dependency) — no new native dependency added.

Large refactor to reduce duplicated code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant