Skip to content

[shared_preferences] switch to ffi/jni pigeon - #12329

Merged
auto-submit[bot] merged 21 commits into
flutter:mainfrom
tarrinneal:spffijni
Oct 17, 2026
Merged

auto-submit[bot] merged 21 commits into
flutter:mainfrom
tarrinneal:spffijni

Conversation

@tarrinneal

@tarrinneal tarrinneal commented Jul 31, 2026 •

Copy link
Copy Markdown
Contributor

Updates Android and Foundation implementations to use the latest pigeon with FFI and JNI. Should not change any underlying behaviors. Primarily to validate the latest pigeon functionality.

@tarrinneal

Copy link
Copy Markdown
Contributor Author

@stuartmorgan-g this is a working example of switching to ffi/jni pigeon. the pr contains all of the pigeon changes, but you can just look at the shared preferences stuff

@tarrinneal tarrinneal added the CICD Run CI/CD label Aug 4, 2026

@stuartmorgan-g stuartmorgan-g left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The iOS part generally looks good, but one question on the binary file. The Android part I have more questions about the structure of main package vs example package.

Comment thread packages/shared_preferences/shared_preferences_android/example/pubspec.yaml Outdated
Comment thread packages/shared_preferences/shared_preferences_android/pigeons/messages.dart Outdated

@stuartmorgan-g stuartmorgan-g left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Android structure is looking good now! One significant remaining issue for actually adopting this PR, but that's not related to the Pigeon generation itself, so in terms of validating the other PR I think we're in good shape here. I'll start on what it looks like should be the final review of the Pigeon PR shortly!

Comment thread packages/shared_preferences/shared_preferences_android/example/pubspec.yaml Outdated
@HostApi()
abstract class SharedPreferencesApi {
/// Removes property from shared preferences data set.
@TaskQueue(type: TaskQueueType.serialBackgroundThread)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the implementations on the native side need to change to manually dispatch to a background thread (like they did before we had task queues, but presumably it's easier in Kotlin) so that we aren't regressing the old issue of doing file access on the main thread.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I should add task queue to the feature list for pigeon

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about that a bit, and I wouldn't recommend it. Task queues were added specifically to address a pre-thread-merge problem where we had Dart code, which already wasn't running on the main thread, and it wanted to trigger background-thread tasks on the native side, but in order to do that everything was bouncing through the main thread because that's how message channels worked, so message channels themselves created main thread contention, and there was nothing clients could do about it. Task queues made the engine avoid the bounce through the main thread.

In an FFI world though, if code is on the main thread on the native side it's because it was being called from main thread Dart code. And so this is just regular programming, where if you want something not to be on the main thread its up to you to move it. We could build a bunch of machinery into Pigeon for that, but there's nothing specific to FFI or Pigeon about the problem, so I don't think it's probably a good idea to add the complexity there.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we dispatch this from an isolate on the dart side?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would talk to the Dart folks before adopting that approach here; IIRC there are still some significant foot-guns around trying to dispatch async FFI calls from a short-lived isolate.

@tarrinneal
tarrinneal marked this pull request as ready for review September 25, 2026 07:55

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the Android and iOS/macOS implementations of the shared_preferences plugin to use Pigeon's JNI and FFI generators, respectively. The changes include updating the threading model to use background dispatchers for asynchronous operations and refactoring plugin registration to improve lifecycle management. The review feedback highlights the need to explicitly manage the lifecycle of the LegacySharedPreferencesPlugin instance to prevent memory leaks and identifies a copy-paste error in a test case that requires correction.

@stuartmorgan-g stuartmorgan-g left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread packages/shared_preferences/shared_preferences_android/CHANGELOG.md Outdated
Comment thread packages/shared_preferences/shared_preferences_foundation/pubspec.yaml Outdated
Comment on lines +8 to +9
sdk: ^3.13.0
flutter: ">=3.47.0"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't ideal, but it is required with ffi pigeon

@Piinks Piinks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumping the minimum Dart SDK constraint enables Dart 3.13 formatting rules. Running the format tool should resolve the CI failure.

}
return value
}
override suspend fun getInt(key: String, options: SharedPreferencesPigeonOptions): Long? =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still wrapping my head around this, so feel free to tell me I am misunderstandning..

In DataStore Preferences, key equality only checks the key name, and preferences[preferencesKey] performs an unchecked generic cast that is erased to Object at runtime.

Previously, getInt, getBool, and getString were non-suspend functions that assigned preferenceFlow.firstOrNull() to a typed local variable inside runBlocking, which caused Kotlin to emit a CHECKCAST instruction and throw ClassCastException when the stored value had a different type.

Now that these methods are suspend functions returning withContext(backgroundDispatcher) { ... preferenceFlow.firstOrNull() }, all intermediate calls and the suspend continuation signature operate on erased Object at the bytecode level, so no cast is emitted in Kotlin (unlike getDouble on line 196, which has an explicit as Double? cast). When invoked from JNI, a type mismatch in DataStore completes the coroutine normally with the wrong Java object, causing the generated JNI bridge in messages_async.g.jni.dart to throw a JNI CastError (bypassing _convertKnownExceptions and leaking the global reference) instead of throwing a Dart TypeError.

Should we ensure the runtime cast happens on the Kotlin side in getInt, getBool, and getString, and add a test for reading a mismatched primitive type (e.g. calling getBool on a String key) in example/integration_test/shared_preferences_test.dart?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! You are correct

@tarrinneal
tarrinneal requested a review from Piinks October 16, 2026 21:14

@Piinks Piinks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants