From adb1c551ec1f38289fc1a713f6f5125ace177fdc Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Apr 2026 10:22:47 -0400 Subject: [PATCH 1/4] [file_selector] Switch to Kotlin Pigeon Replaces the Java Pigeon generator with the Kotlin Pigeon generator, and adjusts the project accordingly: - Adds Kotlin build setings to Gradle. - Updates API signatures for Kotlin/Java differences. - Adds generic Java/Kotlin compat shim to create Result objects from Java, since those haven't been added to the Pigeon generator yet. - Updates tests to use constructors instead of builders, since the Kotlin generator doesn't create builders. - Updates tests to use a Java/Kotlin compat shim to read Kotlin Result values, instead of mocking the Java Pigeon response object. This does not attempt to fix the pre-existing invalid use of null paths described in https://github.com/flutter/flutter/issues/159568, but the behavior does change slightly from an IllegalStateException (which came from the Java builder) to a NullPointerException (from trying to assign to a non-null Kotlin field). Part of https://github.com/flutter/flutter/issues/158287 --- .../file_selector_android/CHANGELOG.md | 4 + .../android/build.gradle.kts | 11 + .../FileSelectorAndroidPlugin.java | 6 +- .../FileSelectorApiImpl.java | 91 ++- .../GeneratedFileSelectorApi.java | 679 ------------------ .../GeneratedFileSelectorApi.kt | 511 +++++++++++++ .../file_selector_android/ResultUtils.kt | 13 + .../FileSelectorAndroidPluginTest.java | 161 +++-- .../file_selector_android/TestResultUtils.kt | 35 + .../lib/src/file_selector_api.g.dart | 234 +++--- .../pigeons/file_selector_api.dart | 7 +- .../file_selector_android/pubspec.yaml | 4 +- 12 files changed, 852 insertions(+), 904 deletions(-) delete mode 100644 packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/GeneratedFileSelectorApi.java create mode 100644 packages/file_selector/file_selector_android/android/src/main/kotlin/dev/flutter/packages/file_selector_android/GeneratedFileSelectorApi.kt create mode 100644 packages/file_selector/file_selector_android/android/src/main/kotlin/dev/flutter/packages/file_selector_android/ResultUtils.kt create mode 100644 packages/file_selector/file_selector_android/android/src/test/kotlin/dev/flutter/packages/file_selector_android/TestResultUtils.kt diff --git a/packages/file_selector/file_selector_android/CHANGELOG.md b/packages/file_selector/file_selector_android/CHANGELOG.md index 070599f8e5d7..f38037fdc48b 100644 --- a/packages/file_selector/file_selector_android/CHANGELOG.md +++ b/packages/file_selector/file_selector_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.2+6 + +* Updates internal implementation to use Kotlin Pigeon. + ## 0.5.2+5 * Updates build files from Groovy to Kotlin. diff --git a/packages/file_selector/file_selector_android/android/build.gradle.kts b/packages/file_selector/file_selector_android/android/build.gradle.kts index 7a9ace9f9f05..680f83d1130a 100644 --- a/packages/file_selector/file_selector_android/android/build.gradle.kts +++ b/packages/file_selector/file_selector_android/android/build.gradle.kts @@ -1,7 +1,10 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + group = "dev.flutter.packages.file_selector_android" version = "1.0" buildscript { + val kotlinVersion = "2.3.0" repositories { google() mavenCentral() @@ -9,6 +12,7 @@ buildscript { dependencies { classpath("com.android.tools.build:gradle:8.13.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") } } @@ -21,6 +25,13 @@ allprojects { plugins { id("com.android.library") + id("kotlin-android") +} + +kotlin { + compilerOptions { + jvmTarget = JvmTarget.fromTarget(JavaVersion.VERSION_17.toString()) + } } android { diff --git a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPlugin.java b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPlugin.java index d8c6c4682aea..fe495c1ba72a 100644 --- a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPlugin.java +++ b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPlugin.java @@ -28,8 +28,7 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { fileSelectorApi = new FileSelectorApiImpl(binding); - GeneratedFileSelectorApi.FileSelectorApi.setUp( - pluginBinding.getBinaryMessenger(), fileSelectorApi); + FileSelectorApi.Companion.setUp(pluginBinding.getBinaryMessenger(), fileSelectorApi); } @Override @@ -45,8 +44,7 @@ public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBindin fileSelectorApi.setActivityPluginBinding(binding); } else { fileSelectorApi = new FileSelectorApiImpl(binding); - GeneratedFileSelectorApi.FileSelectorApi.setUp( - pluginBinding.getBinaryMessenger(), fileSelectorApi); + FileSelectorApi.Companion.setUp(pluginBinding.getBinaryMessenger(), fileSelectorApi); } } diff --git a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java index 2504c9e00b8d..8faaea1df7a3 100644 --- a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java +++ b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/FileSelectorApiImpl.java @@ -31,8 +31,12 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import kotlin.Result; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; +import org.jetbrains.annotations.NotNull; -public class FileSelectorApiImpl implements GeneratedFileSelectorApi.FileSelectorApi { +public class FileSelectorApiImpl implements FileSelectorApi { private static final String TAG = "FileSelectorApiImpl"; // Request code for selecting a file. private static final int OPEN_FILE = 221; @@ -91,9 +95,8 @@ public FileSelectorApiImpl(@NonNull ActivityPluginBinding activityPluginBinding) @Override public void openFile( @Nullable String initialDirectory, - @NonNull GeneratedFileSelectorApi.FileTypes allowedTypes, - @NonNull - GeneratedFileSelectorApi.NullableResult result) { + @NonNull FileTypes allowedTypes, + @NonNull Function1, Unit> callback) { final Intent intent = objectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); @@ -111,32 +114,37 @@ public void onResult(int resultCode, @Nullable Intent data) { final Uri uri = data.getData(); if (uri == null) { // No data retrieved from opening file. - result.error(new Exception("Failed to retrieve data from opening file.")); + ResultUtilsKt.completeWithError( + callback, new Exception("Failed to retrieve data from opening file.")); return; } - final GeneratedFileSelectorApi.FileResponse file = toFileResponse(uri); + final FileResponse file = toFileResponse(uri); if (file != null) { - result.success(file); + ResultUtilsKt.completeWithValue(callback, file); } else { - result.error(new Exception("Failed to read file: " + uri)); + ResultUtilsKt.completeWithError( + callback, new Exception("Failed to read file: " + uri)); } } else { - result.success(null); + ResultUtilsKt.completeWithValue(callback, null); } } }); } catch (Exception exception) { - result.error(exception); + ResultUtilsKt.completeWithError(callback, exception); } } @Override public void openFiles( @Nullable String initialDirectory, - @NonNull GeneratedFileSelectorApi.FileTypes allowedTypes, + @NonNull FileTypes allowedTypes, @NonNull - GeneratedFileSelectorApi.Result> result) { + Function1< + ? super @NotNull Result>, @NotNull + Unit> + callback) { final Intent intent = objectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); @@ -155,46 +163,46 @@ public void onResult(int resultCode, @Nullable Intent data) { // Only one file was returned. final Uri uri = data.getData(); if (uri != null) { - final GeneratedFileSelectorApi.FileResponse file = toFileResponse(uri); + final FileResponse file = toFileResponse(uri); if (file != null) { - result.success(Collections.singletonList(file)); + ResultUtilsKt.completeWithValue(callback, Collections.singletonList(file)); } else { - result.error(new Exception("Failed to read file: " + uri)); + ResultUtilsKt.completeWithError( + callback, new Exception("Failed to read file: " + uri)); } } // Multiple files were returned. final ClipData clipData = data.getClipData(); if (clipData != null) { - final List files = - new ArrayList<>(clipData.getItemCount()); + final List files = new ArrayList<>(clipData.getItemCount()); for (int i = 0; i < clipData.getItemCount(); i++) { final ClipData.Item clipItem = clipData.getItemAt(i); - final GeneratedFileSelectorApi.FileResponse file = - toFileResponse(clipItem.getUri()); + final FileResponse file = toFileResponse(clipItem.getUri()); if (file != null) { files.add(file); } else { - result.error(new Exception("Failed to read file: " + uri)); + ResultUtilsKt.completeWithError( + callback, new Exception("Failed to read file: " + uri)); return; } } - result.success(files); + ResultUtilsKt.completeWithValue(callback, files); } } else { - result.success(new ArrayList<>()); + ResultUtilsKt.completeWithValue(callback, new ArrayList<>()); } } }); } catch (Exception exception) { - result.error(exception); + ResultUtilsKt.completeWithError(callback, exception); } } @Override public void getDirectoryPath( @Nullable String initialDirectory, - @NonNull GeneratedFileSelectorApi.NullableResult result) { + @NonNull Function1, @NotNull Unit> callback) { final Intent intent = objectFactory.newIntent(Intent.ACTION_OPEN_DOCUMENT_TREE); trySetInitialDirectory(intent, initialDirectory); @@ -209,7 +217,8 @@ public void onResult(int resultCode, @Nullable Intent data) { final Uri uri = data.getData(); if (uri == null) { // No data retrieved from opening directory. - result.error(new Exception("Failed to retrieve data from opening directory.")); + ResultUtilsKt.completeWithError( + callback, new Exception("Failed to retrieve data from opening directory.")); return; } @@ -219,17 +228,17 @@ public void onResult(int resultCode, @Nullable Intent data) { try { final String path = FileUtils.getPathFromUri(activityPluginBinding.getActivity(), docUri); - result.success(path); + ResultUtilsKt.completeWithValue(callback, path); } catch (UnsupportedOperationException exception) { - result.error(exception); + ResultUtilsKt.completeWithError(callback, exception); } } else { - result.success(null); + ResultUtilsKt.completeWithValue(callback, null); } } }); } catch (Exception exception) { - result.error(exception); + ResultUtilsKt.completeWithError(callback, exception); } } @@ -240,8 +249,7 @@ public void setActivityPluginBinding(@Nullable ActivityPluginBinding activityPlu // Setting the mimeType with `setType` is required when opening files. This handles setting the // mimeType based on the `mimeTypes` list and converts extensions to mimeTypes. // See https://developer.android.com/guide/components/intents-common#OpenFile - private void setMimeTypes( - @NonNull Intent intent, @NonNull GeneratedFileSelectorApi.FileTypes allowedTypes) { + private void setMimeTypes(@NonNull Intent intent, @NonNull FileTypes allowedTypes) { final Set allMimetypes = new HashSet<>(); allMimetypes.addAll(allowedTypes.getMimeTypes()); allMimetypes.addAll(tryConvertExtensionsToMimetypes(allowedTypes.getExtensions())); @@ -307,7 +315,7 @@ public boolean onActivityResult(int requestCode, int resultCode, @Nullable Inten } @Nullable - GeneratedFileSelectorApi.FileResponse toFileResponse(@NonNull Uri uri) { + FileResponse toFileResponse(@NonNull Uri uri) { if (activityPluginBinding == null) { Log.d(TAG, "Activity is not available."); return null; @@ -351,7 +359,7 @@ GeneratedFileSelectorApi.FileResponse toFileResponse(@NonNull Uri uri) { } String uriPath; - GeneratedFileSelectorApi.FileSelectorNativeException nativeError = null; + FileSelectorNativeException nativeError = null; try { uriPath = FileUtils.getPathFromCopyOfFileFromUri(activityPluginBinding.getActivity(), uri); @@ -370,20 +378,11 @@ GeneratedFileSelectorApi.FileResponse toFileResponse(@NonNull Uri uri) { } catch (IllegalArgumentException e) { uriPath = FILE_SELECTOR_EXCEPTION_PLACEHOLDER_PATH; nativeError = - new GeneratedFileSelectorApi.FileSelectorNativeException.Builder() - .setMessage(e.getMessage() == null ? "" : e.getMessage()) - .setFileSelectorExceptionCode( - GeneratedFileSelectorApi.FileSelectorExceptionCode.ILLEGAL_ARGUMENT_EXCEPTION) - .build(); + new FileSelectorNativeException( + FileSelectorExceptionCode.ILLEGAL_ARGUMENT_EXCEPTION, + e.getMessage() == null ? "" : e.getMessage()); } - return new GeneratedFileSelectorApi.FileResponse.Builder() - .setName(name) - .setBytes(bytes) - .setPath(uriPath) - .setMimeType(contentResolver.getType(uri)) - .setSize(size.longValue()) - .setFileSelectorNativeException(nativeError) - .build(); + return new FileResponse(uriPath, contentResolver.getType(uri), name, size, bytes, nativeError); } } diff --git a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/GeneratedFileSelectorApi.java b/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/GeneratedFileSelectorApi.java deleted file mode 100644 index 9f85509fb45a..000000000000 --- a/packages/file_selector/file_selector_android/android/src/main/java/dev/flutter/packages/file_selector_android/GeneratedFileSelectorApi.java +++ /dev/null @@ -1,679 +0,0 @@ -// Copyright 2013 The Flutter Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -package dev.flutter.packages.file_selector_android; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.CLASS; - -import android.util.Log; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import io.flutter.plugin.common.BasicMessageChannel; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MessageCodec; -import io.flutter.plugin.common.StandardMessageCodec; -import java.io.ByteArrayOutputStream; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -/** Generated class from Pigeon. */ -@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) -public class GeneratedFileSelectorApi { - - /** Error class for passing custom error details to Flutter via a thrown PlatformException. */ - public static class FlutterError extends RuntimeException { - - /** The error code. */ - public final String code; - - /** The error details. Must be a datatype supported by the api codec. */ - public final Object details; - - public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) { - super(message); - this.code = code; - this.details = details; - } - } - - @NonNull - protected static ArrayList wrapError(@NonNull Throwable exception) { - ArrayList errorList = new ArrayList<>(3); - if (exception instanceof FlutterError) { - FlutterError error = (FlutterError) exception; - errorList.add(error.code); - errorList.add(error.getMessage()); - errorList.add(error.details); - } else { - errorList.add(exception.toString()); - errorList.add(exception.getClass().getSimpleName()); - errorList.add( - "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); - } - return errorList; - } - - @Target(METHOD) - @Retention(CLASS) - @interface CanIgnoreReturnValue {} - - public enum FileSelectorExceptionCode { - SECURITY_EXCEPTION(0), - IO_EXCEPTION(1), - ILLEGAL_ARGUMENT_EXCEPTION(2), - ILLEGAL_STATE_EXCEPTION(3); - - final int index; - - FileSelectorExceptionCode(final int index) { - this.index = index; - } - } - - /** Generated class from Pigeon that represents data sent in messages. */ - public static final class FileSelectorNativeException { - private @NonNull FileSelectorExceptionCode fileSelectorExceptionCode; - - public @NonNull FileSelectorExceptionCode getFileSelectorExceptionCode() { - return fileSelectorExceptionCode; - } - - public void setFileSelectorExceptionCode(@NonNull FileSelectorExceptionCode setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"fileSelectorExceptionCode\" is null."); - } - this.fileSelectorExceptionCode = setterArg; - } - - private @NonNull String message; - - public @NonNull String getMessage() { - return message; - } - - public void setMessage(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"message\" is null."); - } - this.message = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - FileSelectorNativeException() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FileSelectorNativeException that = (FileSelectorNativeException) o; - return fileSelectorExceptionCode.equals(that.fileSelectorExceptionCode) - && message.equals(that.message); - } - - @Override - public int hashCode() { - return Objects.hash(fileSelectorExceptionCode, message); - } - - public static final class Builder { - - private @Nullable FileSelectorExceptionCode fileSelectorExceptionCode; - - @CanIgnoreReturnValue - public @NonNull Builder setFileSelectorExceptionCode( - @NonNull FileSelectorExceptionCode setterArg) { - this.fileSelectorExceptionCode = setterArg; - return this; - } - - private @Nullable String message; - - @CanIgnoreReturnValue - public @NonNull Builder setMessage(@NonNull String setterArg) { - this.message = setterArg; - return this; - } - - public @NonNull FileSelectorNativeException build() { - FileSelectorNativeException pigeonReturn = new FileSelectorNativeException(); - pigeonReturn.setFileSelectorExceptionCode(fileSelectorExceptionCode); - pigeonReturn.setMessage(message); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(2); - toListResult.add(fileSelectorExceptionCode); - toListResult.add(message); - return toListResult; - } - - static @NonNull FileSelectorNativeException fromList( - @NonNull ArrayList pigeonVar_list) { - FileSelectorNativeException pigeonResult = new FileSelectorNativeException(); - Object fileSelectorExceptionCode = pigeonVar_list.get(0); - pigeonResult.setFileSelectorExceptionCode( - (FileSelectorExceptionCode) fileSelectorExceptionCode); - Object message = pigeonVar_list.get(1); - pigeonResult.setMessage((String) message); - return pigeonResult; - } - } - - /** Generated class from Pigeon that represents data sent in messages. */ - public static final class FileResponse { - private @NonNull String path; - - public @NonNull String getPath() { - return path; - } - - public void setPath(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"path\" is null."); - } - this.path = setterArg; - } - - private @Nullable String mimeType; - - public @Nullable String getMimeType() { - return mimeType; - } - - public void setMimeType(@Nullable String setterArg) { - this.mimeType = setterArg; - } - - private @Nullable String name; - - public @Nullable String getName() { - return name; - } - - public void setName(@Nullable String setterArg) { - this.name = setterArg; - } - - private @NonNull Long size; - - public @NonNull Long getSize() { - return size; - } - - public void setSize(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"size\" is null."); - } - this.size = setterArg; - } - - private @NonNull byte[] bytes; - - public @NonNull byte[] getBytes() { - return bytes; - } - - public void setBytes(@NonNull byte[] setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"bytes\" is null."); - } - this.bytes = setterArg; - } - - private @Nullable FileSelectorNativeException fileSelectorNativeException; - - public @Nullable FileSelectorNativeException getFileSelectorNativeException() { - return fileSelectorNativeException; - } - - public void setFileSelectorNativeException(@Nullable FileSelectorNativeException setterArg) { - this.fileSelectorNativeException = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - FileResponse() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FileResponse that = (FileResponse) o; - return path.equals(that.path) - && Objects.equals(mimeType, that.mimeType) - && Objects.equals(name, that.name) - && size.equals(that.size) - && Arrays.equals(bytes, that.bytes) - && Objects.equals(fileSelectorNativeException, that.fileSelectorNativeException); - } - - @Override - public int hashCode() { - int pigeonVar_result = Objects.hash(path, mimeType, name, size, fileSelectorNativeException); - pigeonVar_result = 31 * pigeonVar_result + Arrays.hashCode(bytes); - return pigeonVar_result; - } - - public static final class Builder { - - private @Nullable String path; - - @CanIgnoreReturnValue - public @NonNull Builder setPath(@NonNull String setterArg) { - this.path = setterArg; - return this; - } - - private @Nullable String mimeType; - - @CanIgnoreReturnValue - public @NonNull Builder setMimeType(@Nullable String setterArg) { - this.mimeType = setterArg; - return this; - } - - private @Nullable String name; - - @CanIgnoreReturnValue - public @NonNull Builder setName(@Nullable String setterArg) { - this.name = setterArg; - return this; - } - - private @Nullable Long size; - - @CanIgnoreReturnValue - public @NonNull Builder setSize(@NonNull Long setterArg) { - this.size = setterArg; - return this; - } - - private @Nullable byte[] bytes; - - @CanIgnoreReturnValue - public @NonNull Builder setBytes(@NonNull byte[] setterArg) { - this.bytes = setterArg; - return this; - } - - private @Nullable FileSelectorNativeException fileSelectorNativeException; - - @CanIgnoreReturnValue - public @NonNull Builder setFileSelectorNativeException( - @Nullable FileSelectorNativeException setterArg) { - this.fileSelectorNativeException = setterArg; - return this; - } - - public @NonNull FileResponse build() { - FileResponse pigeonReturn = new FileResponse(); - pigeonReturn.setPath(path); - pigeonReturn.setMimeType(mimeType); - pigeonReturn.setName(name); - pigeonReturn.setSize(size); - pigeonReturn.setBytes(bytes); - pigeonReturn.setFileSelectorNativeException(fileSelectorNativeException); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(6); - toListResult.add(path); - toListResult.add(mimeType); - toListResult.add(name); - toListResult.add(size); - toListResult.add(bytes); - toListResult.add(fileSelectorNativeException); - return toListResult; - } - - static @NonNull FileResponse fromList(@NonNull ArrayList pigeonVar_list) { - FileResponse pigeonResult = new FileResponse(); - Object path = pigeonVar_list.get(0); - pigeonResult.setPath((String) path); - Object mimeType = pigeonVar_list.get(1); - pigeonResult.setMimeType((String) mimeType); - Object name = pigeonVar_list.get(2); - pigeonResult.setName((String) name); - Object size = pigeonVar_list.get(3); - pigeonResult.setSize((Long) size); - Object bytes = pigeonVar_list.get(4); - pigeonResult.setBytes((byte[]) bytes); - Object fileSelectorNativeException = pigeonVar_list.get(5); - pigeonResult.setFileSelectorNativeException( - (FileSelectorNativeException) fileSelectorNativeException); - return pigeonResult; - } - } - - /** Generated class from Pigeon that represents data sent in messages. */ - public static final class FileTypes { - private @NonNull List mimeTypes; - - public @NonNull List getMimeTypes() { - return mimeTypes; - } - - public void setMimeTypes(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"mimeTypes\" is null."); - } - this.mimeTypes = setterArg; - } - - private @NonNull List extensions; - - public @NonNull List getExtensions() { - return extensions; - } - - public void setExtensions(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"extensions\" is null."); - } - this.extensions = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - FileTypes() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FileTypes that = (FileTypes) o; - return mimeTypes.equals(that.mimeTypes) && extensions.equals(that.extensions); - } - - @Override - public int hashCode() { - return Objects.hash(mimeTypes, extensions); - } - - public static final class Builder { - - private @Nullable List mimeTypes; - - @CanIgnoreReturnValue - public @NonNull Builder setMimeTypes(@NonNull List setterArg) { - this.mimeTypes = setterArg; - return this; - } - - private @Nullable List extensions; - - @CanIgnoreReturnValue - public @NonNull Builder setExtensions(@NonNull List setterArg) { - this.extensions = setterArg; - return this; - } - - public @NonNull FileTypes build() { - FileTypes pigeonReturn = new FileTypes(); - pigeonReturn.setMimeTypes(mimeTypes); - pigeonReturn.setExtensions(extensions); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(2); - toListResult.add(mimeTypes); - toListResult.add(extensions); - return toListResult; - } - - static @NonNull FileTypes fromList(@NonNull ArrayList pigeonVar_list) { - FileTypes pigeonResult = new FileTypes(); - Object mimeTypes = pigeonVar_list.get(0); - pigeonResult.setMimeTypes((List) mimeTypes); - Object extensions = pigeonVar_list.get(1); - pigeonResult.setExtensions((List) extensions); - return pigeonResult; - } - } - - private static class PigeonCodec extends StandardMessageCodec { - public static final PigeonCodec INSTANCE = new PigeonCodec(); - - private PigeonCodec() {} - - @Override - protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { - switch (type) { - case (byte) 129: - { - Object value = readValue(buffer); - return value == null - ? null - : FileSelectorExceptionCode.values()[((Long) value).intValue()]; - } - case (byte) 130: - return FileSelectorNativeException.fromList((ArrayList) readValue(buffer)); - case (byte) 131: - return FileResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 132: - return FileTypes.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof FileSelectorExceptionCode) { - stream.write(129); - writeValue(stream, value == null ? null : ((FileSelectorExceptionCode) value).index); - } else if (value instanceof FileSelectorNativeException) { - stream.write(130); - writeValue(stream, ((FileSelectorNativeException) value).toList()); - } else if (value instanceof FileResponse) { - stream.write(131); - writeValue(stream, ((FileResponse) value).toList()); - } else if (value instanceof FileTypes) { - stream.write(132); - writeValue(stream, ((FileTypes) value).toList()); - } else { - super.writeValue(stream, value); - } - } - } - - /** Asynchronous error handling return type for non-nullable API method returns. */ - public interface Result { - /** Success case callback method for handling returns. */ - void success(@NonNull T result); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - /** Asynchronous error handling return type for nullable API method returns. */ - public interface NullableResult { - /** Success case callback method for handling returns. */ - void success(@Nullable T result); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - /** Asynchronous error handling return type for void API method returns. */ - public interface VoidResult { - /** Success case callback method for handling returns. */ - void success(); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - /** - * An API to call to native code to select files or directories. - * - *

Generated interface from Pigeon that represents a handler of messages from Flutter. - */ - public interface FileSelectorApi { - /** - * Opens a file dialog for loading files and returns a file path. - * - *

Returns `null` if user cancels the operation. - */ - void openFile( - @Nullable String initialDirectory, - @NonNull FileTypes allowedTypes, - @NonNull NullableResult result); - /** - * Opens a file dialog for loading files and returns a list of file responses chosen by the - * user. - */ - void openFiles( - @Nullable String initialDirectory, - @NonNull FileTypes allowedTypes, - @NonNull Result> result); - /** - * Opens a file dialog for loading directories and returns a directory path. - * - *

Returns `null` if user cancels the operation. - */ - void getDirectoryPath( - @Nullable String initialDirectory, @NonNull NullableResult result); - - /** The codec used by FileSelectorApi. */ - static @NonNull MessageCodec getCodec() { - return PigeonCodec.INSTANCE; - } - /** - * Sets up an instance of `FileSelectorApi` to handle messages through the `binaryMessenger`. - */ - static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable FileSelectorApi api) { - setUp(binaryMessenger, "", api); - } - - static void setUp( - @NonNull BinaryMessenger binaryMessenger, - @NonNull String messageChannelSuffix, - @Nullable FileSelectorApi api) { - messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.file_selector_android.FileSelectorApi.openFile" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - String initialDirectoryArg = (String) args.get(0); - FileTypes allowedTypesArg = (FileTypes) args.get(1); - NullableResult resultCallback = - new NullableResult() { - public void success(FileResponse result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.openFile(initialDirectoryArg, allowedTypesArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.file_selector_android.FileSelectorApi.openFiles" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - String initialDirectoryArg = (String) args.get(0); - FileTypes allowedTypesArg = (FileTypes) args.get(1); - Result> resultCallback = - new Result>() { - public void success(List result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.openFiles(initialDirectoryArg, allowedTypesArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.file_selector_android.FileSelectorApi.getDirectoryPath" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - String initialDirectoryArg = (String) args.get(0); - NullableResult resultCallback = - new NullableResult() { - public void success(String result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.getDirectoryPath(initialDirectoryArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - } - } -} diff --git a/packages/file_selector/file_selector_android/android/src/main/kotlin/dev/flutter/packages/file_selector_android/GeneratedFileSelectorApi.kt b/packages/file_selector/file_selector_android/android/src/main/kotlin/dev/flutter/packages/file_selector_android/GeneratedFileSelectorApi.kt new file mode 100644 index 000000000000..9e525ff8044d --- /dev/null +++ b/packages/file_selector/file_selector_android/android/src/main/kotlin/dev/flutter/packages/file_selector_android/GeneratedFileSelectorApi.kt @@ -0,0 +1,511 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// Autogenerated from Pigeon (v26.3.4), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package dev.flutter.packages.file_selector_android + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +private object GeneratedFileSelectorApiPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf(exception.code, exception.message, exception.details) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)) + } + } + + fun doubleEquals(a: Double, b: Double): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN()) + } + + fun floatEquals(a: Float, b: Float): Boolean { + // Normalize -0.0 to 0.0 and handle NaN equality. + return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN()) + } + + fun doubleHash(d: Double): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (d == 0.0) 0.0 else d + val bits = java.lang.Double.doubleToLongBits(normalized) + return (bits xor (bits ushr 32)).toInt() + } + + fun floatHash(f: Float): Int { + // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes. + val normalized = if (f == 0.0f) 0.0f else f + return java.lang.Float.floatToIntBits(normalized) + } + + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a === b) { + return true + } + if (a == null || b == null) { + return false + } + if (a is ByteArray && b is ByteArray) { + return a.contentEquals(b) + } + if (a is IntArray && b is IntArray) { + return a.contentEquals(b) + } + if (a is LongArray && b is LongArray) { + return a.contentEquals(b) + } + if (a is DoubleArray && b is DoubleArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!doubleEquals(a[i], b[i])) return false + } + return true + } + if (a is FloatArray && b is FloatArray) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!floatEquals(a[i], b[i])) return false + } + return true + } + if (a is Array<*> && b is Array<*>) { + if (a.size != b.size) return false + for (i in a.indices) { + if (!deepEquals(a[i], b[i])) return false + } + return true + } + if (a is List<*> && b is List<*>) { + if (a.size != b.size) return false + val iterA = a.iterator() + val iterB = b.iterator() + while (iterA.hasNext() && iterB.hasNext()) { + if (!deepEquals(iterA.next(), iterB.next())) return false + } + return true + } + if (a is Map<*, *> && b is Map<*, *>) { + if (a.size != b.size) return false + for (entry in a) { + val key = entry.key + var found = false + for (bEntry in b) { + if (deepEquals(key, bEntry.key)) { + if (deepEquals(entry.value, bEntry.value)) { + found = true + break + } else { + return false + } + } + } + if (!found) return false + } + return true + } + if (a is Double && b is Double) { + return doubleEquals(a, b) + } + if (a is Float && b is Float) { + return floatEquals(a, b) + } + return a == b + } + + fun deepHash(value: Any?): Int { + return when (value) { + null -> 0 + is ByteArray -> value.contentHashCode() + is IntArray -> value.contentHashCode() + is LongArray -> value.contentHashCode() + is DoubleArray -> { + var result = 1 + for (item in value) { + result = 31 * result + doubleHash(item) + } + result + } + is FloatArray -> { + var result = 1 + for (item in value) { + result = 31 * result + floatHash(item) + } + result + } + is Array<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is List<*> -> { + var result = 1 + for (item in value) { + result = 31 * result + deepHash(item) + } + result + } + is Map<*, *> -> { + var result = 0 + for (entry in value) { + result += ((deepHash(entry.key) * 31) xor deepHash(entry.value)) + } + result + } + is Double -> doubleHash(value) + is Float -> floatHash(value) + else -> value.hashCode() + } + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError( + val code: String, + override val message: String? = null, + val details: Any? = null +) : RuntimeException() + +enum class FileSelectorExceptionCode(val raw: Int) { + SECURITY_EXCEPTION(0), + IO_EXCEPTION(1), + ILLEGAL_ARGUMENT_EXCEPTION(2), + ILLEGAL_STATE_EXCEPTION(3); + + companion object { + fun ofRaw(raw: Int): FileSelectorExceptionCode? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class FileSelectorNativeException( + val fileSelectorExceptionCode: FileSelectorExceptionCode, + val message: String +) { + companion object { + fun fromList(pigeonVar_list: List): FileSelectorNativeException { + val fileSelectorExceptionCode = pigeonVar_list[0] as FileSelectorExceptionCode + val message = pigeonVar_list[1] as String + return FileSelectorNativeException(fileSelectorExceptionCode, message) + } + } + + fun toList(): List { + return listOf( + fileSelectorExceptionCode, + message, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as FileSelectorNativeException + return GeneratedFileSelectorApiPigeonUtils.deepEquals( + this.fileSelectorExceptionCode, other.fileSelectorExceptionCode) && + GeneratedFileSelectorApiPigeonUtils.deepEquals(this.message, other.message) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = + 31 * result + GeneratedFileSelectorApiPigeonUtils.deepHash(this.fileSelectorExceptionCode) + result = 31 * result + GeneratedFileSelectorApiPigeonUtils.deepHash(this.message) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class FileResponse( + val path: String, + val mimeType: String? = null, + val name: String? = null, + val size: Long, + val bytes: ByteArray, + val fileSelectorNativeException: FileSelectorNativeException? = null +) { + companion object { + fun fromList(pigeonVar_list: List): FileResponse { + val path = pigeonVar_list[0] as String + val mimeType = pigeonVar_list[1] as String? + val name = pigeonVar_list[2] as String? + val size = pigeonVar_list[3] as Long + val bytes = pigeonVar_list[4] as ByteArray + val fileSelectorNativeException = pigeonVar_list[5] as FileSelectorNativeException? + return FileResponse(path, mimeType, name, size, bytes, fileSelectorNativeException) + } + } + + fun toList(): List { + return listOf( + path, + mimeType, + name, + size, + bytes, + fileSelectorNativeException, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as FileResponse + return GeneratedFileSelectorApiPigeonUtils.deepEquals(this.path, other.path) && + GeneratedFileSelectorApiPigeonUtils.deepEquals(this.mimeType, other.mimeType) && + GeneratedFileSelectorApiPigeonUtils.deepEquals(this.name, other.name) && + GeneratedFileSelectorApiPigeonUtils.deepEquals(this.size, other.size) && + GeneratedFileSelectorApiPigeonUtils.deepEquals(this.bytes, other.bytes) && + GeneratedFileSelectorApiPigeonUtils.deepEquals( + this.fileSelectorNativeException, other.fileSelectorNativeException) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + GeneratedFileSelectorApiPigeonUtils.deepHash(this.path) + result = 31 * result + GeneratedFileSelectorApiPigeonUtils.deepHash(this.mimeType) + result = 31 * result + GeneratedFileSelectorApiPigeonUtils.deepHash(this.name) + result = 31 * result + GeneratedFileSelectorApiPigeonUtils.deepHash(this.size) + result = 31 * result + GeneratedFileSelectorApiPigeonUtils.deepHash(this.bytes) + result = + 31 * result + GeneratedFileSelectorApiPigeonUtils.deepHash(this.fileSelectorNativeException) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class FileTypes(val mimeTypes: List, val extensions: List) { + companion object { + fun fromList(pigeonVar_list: List): FileTypes { + val mimeTypes = pigeonVar_list[0] as List + val extensions = pigeonVar_list[1] as List + return FileTypes(mimeTypes, extensions) + } + } + + fun toList(): List { + return listOf( + mimeTypes, + extensions, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as FileTypes + return GeneratedFileSelectorApiPigeonUtils.deepEquals(this.mimeTypes, other.mimeTypes) && + GeneratedFileSelectorApiPigeonUtils.deepEquals(this.extensions, other.extensions) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + GeneratedFileSelectorApiPigeonUtils.deepHash(this.mimeTypes) + result = 31 * result + GeneratedFileSelectorApiPigeonUtils.deepHash(this.extensions) + return result + } +} + +private open class GeneratedFileSelectorApiPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as Long?)?.let { FileSelectorExceptionCode.ofRaw(it.toInt()) } + } + 130.toByte() -> { + return (readValue(buffer) as? List)?.let { FileSelectorNativeException.fromList(it) } + } + 131.toByte() -> { + return (readValue(buffer) as? List)?.let { FileResponse.fromList(it) } + } + 132.toByte() -> { + return (readValue(buffer) as? List)?.let { FileTypes.fromList(it) } + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is FileSelectorExceptionCode -> { + stream.write(129) + writeValue(stream, value.raw.toLong()) + } + is FileSelectorNativeException -> { + stream.write(130) + writeValue(stream, value.toList()) + } + is FileResponse -> { + stream.write(131) + writeValue(stream, value.toList()) + } + is FileTypes -> { + stream.write(132) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + +/** + * An API to call to native code to select files or directories. + * + * Generated interface from Pigeon that represents a handler of messages from Flutter. + */ +interface FileSelectorApi { + /** + * Opens a file dialog for loading files and returns a file path. + * + * Returns `null` if user cancels the operation. + */ + fun openFile( + initialDirectory: String?, + allowedTypes: FileTypes, + callback: (Result) -> Unit + ) + /** + * Opens a file dialog for loading files and returns a list of file responses chosen by the user. + */ + fun openFiles( + initialDirectory: String?, + allowedTypes: FileTypes, + callback: (Result>) -> Unit + ) + /** + * Opens a file dialog for loading directories and returns a directory path. + * + * Returns `null` if user cancels the operation. + */ + fun getDirectoryPath(initialDirectory: String?, callback: (Result) -> Unit) + + companion object { + /** The codec used by FileSelectorApi. */ + val codec: MessageCodec by lazy { GeneratedFileSelectorApiPigeonCodec() } + /** + * Sets up an instance of `FileSelectorApi` to handle messages through the `binaryMessenger`. + */ + @JvmOverloads + fun setUp( + binaryMessenger: BinaryMessenger, + api: FileSelectorApi?, + messageChannelSuffix: String = "" + ) { + val separatedMessageChannelSuffix = + if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.file_selector_android.FileSelectorApi.openFile$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val initialDirectoryArg = args[0] as String? + val allowedTypesArg = args[1] as FileTypes + api.openFile(initialDirectoryArg, allowedTypesArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedFileSelectorApiPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(GeneratedFileSelectorApiPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.file_selector_android.FileSelectorApi.openFiles$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val initialDirectoryArg = args[0] as String? + val allowedTypesArg = args[1] as FileTypes + api.openFiles(initialDirectoryArg, allowedTypesArg) { result: Result> + -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedFileSelectorApiPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(GeneratedFileSelectorApiPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.file_selector_android.FileSelectorApi.getDirectoryPath$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val initialDirectoryArg = args[0] as String? + api.getDirectoryPath(initialDirectoryArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeneratedFileSelectorApiPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(GeneratedFileSelectorApiPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/packages/file_selector/file_selector_android/android/src/main/kotlin/dev/flutter/packages/file_selector_android/ResultUtils.kt b/packages/file_selector/file_selector_android/android/src/main/kotlin/dev/flutter/packages/file_selector_android/ResultUtils.kt new file mode 100644 index 000000000000..8f10b9e5d240 --- /dev/null +++ b/packages/file_selector/file_selector_android/android/src/main/kotlin/dev/flutter/packages/file_selector_android/ResultUtils.kt @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.packages.file_selector_android + +fun completeWithError(callback: (Result<@JvmSuppressWildcards T>) -> Unit, failure: Throwable) { + callback(Result.failure(failure)) +} + +fun completeWithValue(callback: (Result<@JvmSuppressWildcards T>) -> Unit, value: T) { + callback(Result.success(value)) +} diff --git a/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java b/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java index 21b6dc445dee..bab35726adef 100644 --- a/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java +++ b/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java @@ -7,6 +7,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -27,6 +28,7 @@ import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.PluginRegistry; +import io.flutter.plugins.googlesignin.ResultCompat; import java.io.DataInputStream; import java.io.FileNotFoundException; import java.io.InputStream; @@ -97,15 +99,22 @@ public void openFileReturnsSuccessfully() throws FileNotFoundException { mockObjectFactory, (version) -> Build.VERSION.SDK_INT >= version); - final GeneratedFileSelectorApi.NullableResult mockResult = - mock(GeneratedFileSelectorApi.NullableResult.class); + final Boolean[] callbackCalled = new Boolean[1]; fileSelectorApi.openFile( null, - new GeneratedFileSelectorApi.FileTypes.Builder() - .setMimeTypes(Collections.emptyList()) - .setExtensions(Collections.emptyList()) - .build(), - mockResult); + new FileTypes(Collections.emptyList(), Collections.emptyList()), + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + FileResponse file = reply.getOrNull(); + assertNotNull(file); + assertEquals(30, file.getBytes().length); + assertEquals("text/plain", file.getMimeType()); + assertEquals("filename", file.getName()); + assertEquals(30L, file.getSize()); + assertEquals(mockUriPath, file.getPath()); + return null; + })); verify(mockIntent).addCategory(Intent.CATEGORY_OPENABLE); verify(mockActivity).startActivityForResult(mockIntent, 221); @@ -118,16 +127,7 @@ public void openFileReturnsSuccessfully() throws FileNotFoundException { when(resultMockIntent.getData()).thenReturn(mockUri); listenerArgumentCaptor.getValue().onActivityResult(221, Activity.RESULT_OK, resultMockIntent); - final ArgumentCaptor fileCaptor = - ArgumentCaptor.forClass(GeneratedFileSelectorApi.FileResponse.class); - verify(mockResult).success(fileCaptor.capture()); - - final GeneratedFileSelectorApi.FileResponse file = fileCaptor.getValue(); - assertEquals(file.getBytes().length, 30); - assertEquals(file.getMimeType(), "text/plain"); - assertEquals(file.getName(), "filename"); - assertEquals(file.getSize(), (Long) 30L); - assertEquals(file.getPath(), mockUriPath); + assertTrue(callbackCalled[0]); } } @@ -162,15 +162,30 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { mockObjectFactory, (version) -> Build.VERSION.SDK_INT >= version); - final GeneratedFileSelectorApi.Result mockResult = - mock(GeneratedFileSelectorApi.Result.class); + final Boolean[] callbackCalled = new Boolean[1]; fileSelectorApi.openFiles( null, - new GeneratedFileSelectorApi.FileTypes.Builder() - .setMimeTypes(Collections.emptyList()) - .setExtensions(Collections.emptyList()) - .build(), - mockResult); + new FileTypes(Collections.emptyList(), Collections.emptyList()), + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + List fileList = reply.getOrNull(); + assertNotNull(fileList); + FileResponse file1 = fileList.get(0); + assertEquals(30, file1.getBytes().length); + assertEquals("text/plain", file1.getMimeType()); + assertEquals("filename", file1.getName()); + assertEquals(30L, file1.getSize()); + assertEquals(mockUriPath, file1.getPath()); + + FileResponse file2 = fileList.get(1); + assertEquals(40, file2.getBytes().length); + assertEquals("image/jpg", file2.getMimeType()); + assertEquals("filename2", file2.getName()); + assertEquals(40L, file2.getSize()); + assertEquals(mockUri2Path, file2.getPath()); + return null; + })); verify(mockIntent).addCategory(Intent.CATEGORY_OPENABLE); verify(mockIntent).putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); @@ -196,21 +211,7 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { listenerArgumentCaptor.getValue().onActivityResult(222, Activity.RESULT_OK, resultMockIntent); - final ArgumentCaptor fileListCaptor = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(fileListCaptor.capture()); - - final List fileList = fileListCaptor.getValue(); - assertEquals(fileList.get(0).getBytes().length, 30); - assertEquals(fileList.get(0).getMimeType(), "text/plain"); - assertEquals(fileList.get(0).getName(), "filename"); - assertEquals(fileList.get(0).getSize(), (Long) 30L); - assertEquals(fileList.get(0).getPath(), mockUriPath); - - assertEquals(fileList.get(1).getBytes().length, 40); - assertEquals(fileList.get(1).getMimeType(), "image/jpg"); - assertEquals(fileList.get(1).getName(), "filename2"); - assertEquals(fileList.get(1).getSize(), (Long) 40L); - assertEquals(fileList.get(1).getPath(), mockUri2Path); + assertTrue(callbackCalled[0]); } } @@ -222,7 +223,7 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { @SuppressWarnings({"rawtypes", "unchecked"}) @Test public void - openFileThrowsIllegalStateException_whenSecurityExceptionInGetPathFromCopyOfFileFromUri() + openFileThrowsNullPointerException_whenSecurityExceptionInGetPathFromCopyOfFileFromUri() throws FileNotFoundException { try (MockedStatic mockedFileUtils = mockStatic(FileUtils.class)) { @@ -253,15 +254,13 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { mockObjectFactory, (version) -> Build.VERSION.SDK_INT >= version); - final GeneratedFileSelectorApi.Result mockResult = - mock(GeneratedFileSelectorApi.Result.class); fileSelectorApi.openFiles( null, - new GeneratedFileSelectorApi.FileTypes.Builder() - .setMimeTypes(Collections.emptyList()) - .setExtensions(Collections.emptyList()) - .build(), - mockResult); + new FileTypes(Collections.emptyList(), Collections.emptyList()), + ResultCompat.asCompatCallback( + (reply) -> { + return null; + })); verify(mockIntent).addCategory(Intent.CATEGORY_OPENABLE); verify(mockIntent).putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); @@ -286,7 +285,7 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { when(resultMockIntent.getClipData()).thenReturn(mockClipData); assertThrows( - IllegalStateException.class, + NullPointerException.class, () -> listenerArgumentCaptor .getValue() @@ -318,15 +317,19 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { mockObjectFactory, (version) -> Build.VERSION.SDK_INT >= version); - final GeneratedFileSelectorApi.NullableResult mockResult = - mock(GeneratedFileSelectorApi.NullableResult.class); + final Boolean[] callbackCalled = new Boolean[1]; fileSelectorApi.openFile( null, - new GeneratedFileSelectorApi.FileTypes.Builder() - .setMimeTypes(Collections.emptyList()) - .setExtensions(Collections.emptyList()) - .build(), - mockResult); + new FileTypes(Collections.emptyList(), Collections.emptyList()), + ResultCompat.asCompatCallback( + (reply) -> { + callbackCalled[0] = true; + final FileResponse file = reply.getOrNull(); + assertNotNull(file); + assertNotNull(file.getFileSelectorNativeException()); + assertEquals(FileUtils.FILE_SELECTOR_EXCEPTION_PLACEHOLDER_PATH, file.getPath()); + return null; + })); verify(mockIntent).addCategory(Intent.CATEGORY_OPENABLE); verify(mockActivity).startActivityForResult(mockIntent, 221); @@ -339,13 +342,7 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { when(resultMockIntent.getData()).thenReturn(mockUri); listenerArgumentCaptor.getValue().onActivityResult(221, Activity.RESULT_OK, resultMockIntent); - final ArgumentCaptor fileCaptor = - ArgumentCaptor.forClass(GeneratedFileSelectorApi.FileResponse.class); - verify(mockResult).success(fileCaptor.capture()); - - final GeneratedFileSelectorApi.FileResponse file = fileCaptor.getValue(); - assertNotNull(file.getFileSelectorNativeException()); - assertEquals(file.getPath(), FileUtils.FILE_SELECTOR_EXCEPTION_PLACEHOLDER_PATH); + assertTrue(callbackCalled[0]); } } @@ -375,15 +372,20 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { mockObjectFactory, (version) -> Build.VERSION.SDK_INT >= version); - final GeneratedFileSelectorApi.Result mockResult = - mock(GeneratedFileSelectorApi.Result.class); + final Boolean[] callbackCalled = new Boolean[1]; fileSelectorApi.openFiles( null, - new GeneratedFileSelectorApi.FileTypes.Builder() - .setMimeTypes(Collections.emptyList()) - .setExtensions(Collections.emptyList()) - .build(), - mockResult); + new FileTypes(Collections.emptyList(), Collections.emptyList()), + ResultCompat.asCompatCallback( + (reply) -> { + callbackCalled[0] = true; + final List files = reply.getOrNull(); + assertNotNull(files); + final FileResponse file = files.get(0); + assertNotNull(file.getFileSelectorNativeException()); + assertEquals(FileUtils.FILE_SELECTOR_EXCEPTION_PLACEHOLDER_PATH, file.getPath()); + return null; + })); verify(mockIntent).addCategory(Intent.CATEGORY_OPENABLE); verify(mockIntent).putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); @@ -405,12 +407,7 @@ public void openFilesReturnsSuccessfully() throws FileNotFoundException { listenerArgumentCaptor.getValue().onActivityResult(222, Activity.RESULT_OK, resultMockIntent); - final ArgumentCaptor fileListCaptor = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(fileListCaptor.capture()); - - final List fileList = fileListCaptor.getValue(); - assertEquals(fileList.get(0).getPath(), FileUtils.FILE_SELECTOR_EXCEPTION_PLACEHOLDER_PATH); - assertNotNull(fileList.get(0).getFileSelectorNativeException()); + assertTrue(callbackCalled[0]); } } @@ -445,9 +442,15 @@ public void getDirectoryPathReturnsSuccessfully() { mockObjectFactory, (version) -> Build.VERSION.SDK_INT >= version); - final GeneratedFileSelectorApi.NullableResult mockResult = - mock(GeneratedFileSelectorApi.NullableResult.class); - fileSelectorApi.getDirectoryPath(null, mockResult); + final Boolean[] callbackCalled = new Boolean[1]; + fileSelectorApi.getDirectoryPath( + null, + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertEquals(mockUriPath, reply.getOrNull()); + return null; + })); verify(mockActivity).startActivityForResult(mockIntent, 223); @@ -461,7 +464,7 @@ public void getDirectoryPathReturnsSuccessfully() { .getValue() .onActivityResult(223, Activity.RESULT_OK, resultMockIntent); - verify(mockResult).success(mockUriPath); + assertTrue(callbackCalled[0]); } } } diff --git a/packages/file_selector/file_selector_android/android/src/test/kotlin/dev/flutter/packages/file_selector_android/TestResultUtils.kt b/packages/file_selector/file_selector_android/android/src/test/kotlin/dev/flutter/packages/file_selector_android/TestResultUtils.kt new file mode 100644 index 000000000000..a3b50702f04e --- /dev/null +++ b/packages/file_selector/file_selector_android/android/src/test/kotlin/dev/flutter/packages/file_selector_android/TestResultUtils.kt @@ -0,0 +1,35 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlesignin + +/** Wraps Kotlin Result for use in Java unit tests. */ +@Suppress("UNCHECKED_CAST") +class ResultCompat(private val result: Result) { + private val value: T? = result.getOrNull() + private val exception = result.exceptionOrNull() + val isSuccess = result.isSuccess + val isFailure = result.isFailure + + companion object { + @JvmStatic + fun success(value: T, callback: Any) { + val castedCallback: (Result) -> Unit = callback as (Result) -> Unit + castedCallback(Result.success(value)) + } + + @JvmStatic + fun asCompatCallback(result: (ResultCompat) -> Unit): (Result) -> Unit { + return { result(ResultCompat(it)) } + } + } + + fun getOrNull(): T? { + return value + } + + fun exceptionOrNull(): Throwable? { + return exception + } +} diff --git a/packages/file_selector/file_selector_android/lib/src/file_selector_api.g.dart b/packages/file_selector/file_selector_android/lib/src/file_selector_api.g.dart index 07663232cdea..2d76187bea81 100644 --- a/packages/file_selector/file_selector_android/lib/src/file_selector_api.g.dart +++ b/packages/file_selector/file_selector_android/lib/src/file_selector_api.g.dart @@ -1,24 +1,52 @@ // Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// Autogenerated from Pigeon (v26.3.4), do not edit directly. // See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers +// ignore_for_file: unused_import, unused_shown_name +// ignore_for_file: type=lint import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'dart:typed_data' show Float64List, Int32List, Int64List; -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/services.dart'; - -PlatformException _createConnectionError(String channelName) { - return PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel: "$channelName".', - ); +import 'package:meta/meta.dart' show immutable, protected, visibleForTesting; + +Object? _extractReplyValueOrThrow( + List? replyList, + String channelName, { + required bool isNullValid, +}) { + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } + return replyList.firstOrNull; } bool _deepEquals(Object? a, Object? b) { + if (identical(a, b)) { + return true; + } + if (a is double && b is double) { + if (a.isNaN && b.isNaN) { + return true; + } + return a == b; + } if (a is List && b is List) { return a.length == b.length && a.indexed.every( @@ -26,16 +54,52 @@ bool _deepEquals(Object? a, Object? b) { ); } if (a is Map && b is Map) { - return a.length == b.length && - a.entries.every( - (MapEntry entry) => - (b as Map).containsKey(entry.key) && - _deepEquals(entry.value, b[entry.key]), - ); + if (a.length != b.length) { + return false; + } + for (final MapEntry entryA in a.entries) { + bool found = false; + for (final MapEntry entryB in b.entries) { + if (_deepEquals(entryA.key, entryB.key)) { + if (_deepEquals(entryA.value, entryB.value)) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return true; } return a == b; } +int _deepHash(Object? value) { + if (value is List) { + return Object.hashAll(value.map(_deepHash)); + } + if (value is Map) { + int result = 0; + for (final MapEntry entry in value.entries) { + result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value); + } + return result; + } + if (value is double && value.isNaN) { + // Normalize NaN to a consistent hash. + return 0x7FF8000000000000.hashCode; + } + if (value is double && value == 0.0) { + // Normalize -0.0 to 0.0 so they have the same hash code. + return 0.0.hashCode; + } + return value.hashCode; +} + enum FileSelectorExceptionCode { securityException, ioException, @@ -79,12 +143,16 @@ class FileSelectorNativeException { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals( + fileSelectorExceptionCode, + other.fileSelectorExceptionCode, + ) && + _deepEquals(message, other.message); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class FileResponse { @@ -145,12 +213,20 @@ class FileResponse { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(path, other.path) && + _deepEquals(mimeType, other.mimeType) && + _deepEquals(name, other.name) && + _deepEquals(size, other.size) && + _deepEquals(bytes, other.bytes) && + _deepEquals( + fileSelectorNativeException, + other.fileSelectorNativeException, + ); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class FileTypes { @@ -171,8 +247,8 @@ class FileTypes { static FileTypes decode(Object result) { result as List; return FileTypes( - mimeTypes: (result[0] as List?)!.cast(), - extensions: (result[1] as List?)!.cast(), + mimeTypes: (result[0]! as List).cast(), + extensions: (result[1]! as List).cast(), ); } @@ -185,12 +261,13 @@ class FileTypes { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(mimeTypes, other.mimeTypes) && + _deepEquals(extensions, other.extensions); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class _PigeonCodec extends StandardMessageCodec { @@ -221,7 +298,7 @@ class _PigeonCodec extends StandardMessageCodec { Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 129: - final int? value = readValue(buffer) as int?; + final value = readValue(buffer) as int?; return value == null ? null : FileSelectorExceptionCode.values[value]; case 130: return FileSelectorNativeException.decode(readValue(buffer)!); @@ -260,30 +337,24 @@ class FileSelectorApi { String? initialDirectory, FileTypes allowedTypes, ) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.file_selector_android.FileSelectorApi.openFile$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [initialDirectory, allowedTypes], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as FileResponse?); - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + return pigeonVar_replyValue as FileResponse?; } /// Opens a file dialog for loading files and returns a list of file responses @@ -292,64 +363,47 @@ class FileSelectorApi { String? initialDirectory, FileTypes allowedTypes, ) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.file_selector_android.FileSelectorApi.openFiles$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [initialDirectory, allowedTypes], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as List?)!.cast(); - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return (pigeonVar_replyValue! as List).cast(); } /// Opens a file dialog for loading directories and returns a directory path. /// /// Returns `null` if user cancels the operation. Future getDirectoryPath(String? initialDirectory) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.file_selector_android.FileSelectorApi.getDirectoryPath$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final Future pigeonVar_sendFuture = pigeonVar_channel.send( [initialDirectory], ); - final List? pigeonVar_replyList = - await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return (pigeonVar_replyList[0] as String?); - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + return pigeonVar_replyValue as String?; } } diff --git a/packages/file_selector/file_selector_android/pigeons/file_selector_api.dart b/packages/file_selector/file_selector_android/pigeons/file_selector_api.dart index 1aa94ae1bc9a..63900221f0cd 100644 --- a/packages/file_selector/file_selector_android/pigeons/file_selector_api.dart +++ b/packages/file_selector/file_selector_android/pigeons/file_selector_api.dart @@ -7,11 +7,10 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( dartOut: 'lib/src/file_selector_api.g.dart', - javaOut: - 'android/src/main/java/dev/flutter/packages/file_selector_android/GeneratedFileSelectorApi.java', - javaOptions: JavaOptions( + kotlinOut: + 'android/src/main/kotlin/dev/flutter/packages/file_selector_android/GeneratedFileSelectorApi.kt', + kotlinOptions: KotlinOptions( package: 'dev.flutter.packages.file_selector_android', - className: 'GeneratedFileSelectorApi', ), copyrightHeader: 'pigeons/copyright.txt', ), diff --git a/packages/file_selector/file_selector_android/pubspec.yaml b/packages/file_selector/file_selector_android/pubspec.yaml index 80440caaa287..49c54072346f 100644 --- a/packages/file_selector/file_selector_android/pubspec.yaml +++ b/packages/file_selector/file_selector_android/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_android description: Android implementation of the file_selector package. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.5.2+5 +version: 0.5.2+6 environment: sdk: ^3.9.0 @@ -28,7 +28,7 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.4.4 - pigeon: ^26.1.0 + pigeon: ^26.3.4 topics: - files From 2107341934834c12112db569cc18845200a30591 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Apr 2026 13:41:54 -0400 Subject: [PATCH 2/4] Fix test util package --- .../flutter/packages/file_selector_android/TestResultUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/file_selector/file_selector_android/android/src/test/kotlin/dev/flutter/packages/file_selector_android/TestResultUtils.kt b/packages/file_selector/file_selector_android/android/src/test/kotlin/dev/flutter/packages/file_selector_android/TestResultUtils.kt index a3b50702f04e..c726be1ba198 100644 --- a/packages/file_selector/file_selector_android/android/src/test/kotlin/dev/flutter/packages/file_selector_android/TestResultUtils.kt +++ b/packages/file_selector/file_selector_android/android/src/test/kotlin/dev/flutter/packages/file_selector_android/TestResultUtils.kt @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package io.flutter.plugins.googlesignin +package dev.flutter.packages.file_selector_android /** Wraps Kotlin Result for use in Java unit tests. */ @Suppress("UNCHECKED_CAST") From 5f1cf29a1700024eb76595c02587f10776863e2f Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Apr 2026 13:42:31 -0400 Subject: [PATCH 3/4] Add meta --- packages/file_selector/file_selector_android/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/file_selector/file_selector_android/pubspec.yaml b/packages/file_selector/file_selector_android/pubspec.yaml index 49c54072346f..d3658ea68e46 100644 --- a/packages/file_selector/file_selector_android/pubspec.yaml +++ b/packages/file_selector/file_selector_android/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: file_selector_platform_interface: ^2.5.0 flutter: sdk: flutter + meta: ^1.10.0 plugin_platform_interface: ^2.1.7 dev_dependencies: From 04a8ab02468d03e3994d09b68a217c7b769d585a Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 9 Apr 2026 13:44:07 -0400 Subject: [PATCH 4/4] Fix import --- .../file_selector_android/FileSelectorAndroidPluginTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java b/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java index bab35726adef..803fb2696432 100644 --- a/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java +++ b/packages/file_selector/file_selector_android/android/src/test/java/dev/flutter/packages/file_selector_android/FileSelectorAndroidPluginTest.java @@ -28,7 +28,6 @@ import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugins.googlesignin.ResultCompat; import java.io.DataInputStream; import java.io.FileNotFoundException; import java.io.InputStream;