From 69f2a755387f92dec323d6c4c30859f9a684b362 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 10 Apr 2026 14:18:49 -0400 Subject: [PATCH 1/9] Add Kotlin to Gradle --- .../image_picker_android/android/build.gradle.kts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/image_picker/image_picker_android/android/build.gradle.kts b/packages/image_picker/image_picker_android/android/build.gradle.kts index 5aeda345747..c6d09be0e91 100644 --- a/packages/image_picker/image_picker_android/android/build.gradle.kts +++ b/packages/image_picker/image_picker_android/android/build.gradle.kts @@ -1,7 +1,10 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + group = "io.flutter.plugins.imagepicker" version = "1.0-SNAPSHOT" buildscript { + val kotlinVersion = "2.3.20" 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 { From 890466c70a0b5bcd2c46f5918b352195669b60fa Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 10 Apr 2026 14:20:46 -0400 Subject: [PATCH 2/9] Switch Pigeon generation --- .../flutter/plugins/imagepicker/Messages.java | 1057 ----------------- .../flutter/plugins/imagepicker/Messages.kt | 763 ++++++++++++ .../lib/src/messages.g.dart | 289 +++-- .../pigeons/messages.dart | 6 +- .../image_picker_android/pubspec.yaml | 2 +- 5 files changed, 927 insertions(+), 1190 deletions(-) delete mode 100644 packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java create mode 100644 packages/image_picker/image_picker_android/android/src/main/kotlin/io/flutter/plugins/imagepicker/Messages.kt diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java deleted file mode 100644 index bf582fa4d35..00000000000 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java +++ /dev/null @@ -1,1057 +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 io.flutter.plugins.imagepicker; - -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.List; -import java.util.Objects; - -/** Generated class from Pigeon. */ -@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) -public class Messages { - - /** 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 SourceCamera { - REAR(0), - FRONT(1); - - final int index; - - SourceCamera(final int index) { - this.index = index; - } - } - - public enum SourceType { - CAMERA(0), - GALLERY(1); - - final int index; - - SourceType(final int index) { - this.index = index; - } - } - - public enum CacheRetrievalType { - IMAGE(0), - VIDEO(1); - - final int index; - - CacheRetrievalType(final int index) { - this.index = index; - } - } - - /** Generated class from Pigeon that represents data sent in messages. */ - public static final class GeneralOptions { - private @NonNull Boolean allowMultiple; - - public @NonNull Boolean getAllowMultiple() { - return allowMultiple; - } - - public void setAllowMultiple(@NonNull Boolean setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"allowMultiple\" is null."); - } - this.allowMultiple = setterArg; - } - - private @NonNull Boolean usePhotoPicker; - - public @NonNull Boolean getUsePhotoPicker() { - return usePhotoPicker; - } - - public void setUsePhotoPicker(@NonNull Boolean setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"usePhotoPicker\" is null."); - } - this.usePhotoPicker = setterArg; - } - - private @Nullable Long limit; - - public @Nullable Long getLimit() { - return limit; - } - - public void setLimit(@Nullable Long setterArg) { - this.limit = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - GeneralOptions() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - GeneralOptions that = (GeneralOptions) o; - return allowMultiple.equals(that.allowMultiple) - && usePhotoPicker.equals(that.usePhotoPicker) - && Objects.equals(limit, that.limit); - } - - @Override - public int hashCode() { - return Objects.hash(allowMultiple, usePhotoPicker, limit); - } - - public static final class Builder { - - private @Nullable Boolean allowMultiple; - - @CanIgnoreReturnValue - public @NonNull Builder setAllowMultiple(@NonNull Boolean setterArg) { - this.allowMultiple = setterArg; - return this; - } - - private @Nullable Boolean usePhotoPicker; - - @CanIgnoreReturnValue - public @NonNull Builder setUsePhotoPicker(@NonNull Boolean setterArg) { - this.usePhotoPicker = setterArg; - return this; - } - - private @Nullable Long limit; - - @CanIgnoreReturnValue - public @NonNull Builder setLimit(@Nullable Long setterArg) { - this.limit = setterArg; - return this; - } - - public @NonNull GeneralOptions build() { - GeneralOptions pigeonReturn = new GeneralOptions(); - pigeonReturn.setAllowMultiple(allowMultiple); - pigeonReturn.setUsePhotoPicker(usePhotoPicker); - pigeonReturn.setLimit(limit); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(3); - toListResult.add(allowMultiple); - toListResult.add(usePhotoPicker); - toListResult.add(limit); - return toListResult; - } - - static @NonNull GeneralOptions fromList(@NonNull ArrayList pigeonVar_list) { - GeneralOptions pigeonResult = new GeneralOptions(); - Object allowMultiple = pigeonVar_list.get(0); - pigeonResult.setAllowMultiple((Boolean) allowMultiple); - Object usePhotoPicker = pigeonVar_list.get(1); - pigeonResult.setUsePhotoPicker((Boolean) usePhotoPicker); - Object limit = pigeonVar_list.get(2); - pigeonResult.setLimit((Long) limit); - return pigeonResult; - } - } - - /** - * Options for image selection and output. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class ImageSelectionOptions { - /** If set, the max width that the image should be resized to fit in. */ - private @Nullable Double maxWidth; - - public @Nullable Double getMaxWidth() { - return maxWidth; - } - - public void setMaxWidth(@Nullable Double setterArg) { - this.maxWidth = setterArg; - } - - /** If set, the max height that the image should be resized to fit in. */ - private @Nullable Double maxHeight; - - public @Nullable Double getMaxHeight() { - return maxHeight; - } - - public void setMaxHeight(@Nullable Double setterArg) { - this.maxHeight = setterArg; - } - - /** - * The quality of the output image, from 0-100. - * - *

100 indicates original quality. - */ - private @NonNull Long quality; - - public @NonNull Long getQuality() { - return quality; - } - - public void setQuality(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"quality\" is null."); - } - this.quality = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - ImageSelectionOptions() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ImageSelectionOptions that = (ImageSelectionOptions) o; - return Objects.equals(maxWidth, that.maxWidth) - && Objects.equals(maxHeight, that.maxHeight) - && quality.equals(that.quality); - } - - @Override - public int hashCode() { - return Objects.hash(maxWidth, maxHeight, quality); - } - - public static final class Builder { - - private @Nullable Double maxWidth; - - @CanIgnoreReturnValue - public @NonNull Builder setMaxWidth(@Nullable Double setterArg) { - this.maxWidth = setterArg; - return this; - } - - private @Nullable Double maxHeight; - - @CanIgnoreReturnValue - public @NonNull Builder setMaxHeight(@Nullable Double setterArg) { - this.maxHeight = setterArg; - return this; - } - - private @Nullable Long quality; - - @CanIgnoreReturnValue - public @NonNull Builder setQuality(@NonNull Long setterArg) { - this.quality = setterArg; - return this; - } - - public @NonNull ImageSelectionOptions build() { - ImageSelectionOptions pigeonReturn = new ImageSelectionOptions(); - pigeonReturn.setMaxWidth(maxWidth); - pigeonReturn.setMaxHeight(maxHeight); - pigeonReturn.setQuality(quality); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(3); - toListResult.add(maxWidth); - toListResult.add(maxHeight); - toListResult.add(quality); - return toListResult; - } - - static @NonNull ImageSelectionOptions fromList(@NonNull ArrayList pigeonVar_list) { - ImageSelectionOptions pigeonResult = new ImageSelectionOptions(); - Object maxWidth = pigeonVar_list.get(0); - pigeonResult.setMaxWidth((Double) maxWidth); - Object maxHeight = pigeonVar_list.get(1); - pigeonResult.setMaxHeight((Double) maxHeight); - Object quality = pigeonVar_list.get(2); - pigeonResult.setQuality((Long) quality); - return pigeonResult; - } - } - - /** Generated class from Pigeon that represents data sent in messages. */ - public static final class MediaSelectionOptions { - private @NonNull ImageSelectionOptions imageSelectionOptions; - - public @NonNull ImageSelectionOptions getImageSelectionOptions() { - return imageSelectionOptions; - } - - public void setImageSelectionOptions(@NonNull ImageSelectionOptions setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"imageSelectionOptions\" is null."); - } - this.imageSelectionOptions = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - MediaSelectionOptions() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - MediaSelectionOptions that = (MediaSelectionOptions) o; - return imageSelectionOptions.equals(that.imageSelectionOptions); - } - - @Override - public int hashCode() { - return Objects.hash(imageSelectionOptions); - } - - public static final class Builder { - - private @Nullable ImageSelectionOptions imageSelectionOptions; - - @CanIgnoreReturnValue - public @NonNull Builder setImageSelectionOptions(@NonNull ImageSelectionOptions setterArg) { - this.imageSelectionOptions = setterArg; - return this; - } - - public @NonNull MediaSelectionOptions build() { - MediaSelectionOptions pigeonReturn = new MediaSelectionOptions(); - pigeonReturn.setImageSelectionOptions(imageSelectionOptions); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(1); - toListResult.add(imageSelectionOptions); - return toListResult; - } - - static @NonNull MediaSelectionOptions fromList(@NonNull ArrayList pigeonVar_list) { - MediaSelectionOptions pigeonResult = new MediaSelectionOptions(); - Object imageSelectionOptions = pigeonVar_list.get(0); - pigeonResult.setImageSelectionOptions((ImageSelectionOptions) imageSelectionOptions); - return pigeonResult; - } - } - - /** - * Options for image selection and output. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class VideoSelectionOptions { - /** The maximum desired length for the video, in seconds. */ - private @Nullable Long maxDurationSeconds; - - public @Nullable Long getMaxDurationSeconds() { - return maxDurationSeconds; - } - - public void setMaxDurationSeconds(@Nullable Long setterArg) { - this.maxDurationSeconds = setterArg; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - VideoSelectionOptions that = (VideoSelectionOptions) o; - return Objects.equals(maxDurationSeconds, that.maxDurationSeconds); - } - - @Override - public int hashCode() { - return Objects.hash(maxDurationSeconds); - } - - public static final class Builder { - - private @Nullable Long maxDurationSeconds; - - @CanIgnoreReturnValue - public @NonNull Builder setMaxDurationSeconds(@Nullable Long setterArg) { - this.maxDurationSeconds = setterArg; - return this; - } - - public @NonNull VideoSelectionOptions build() { - VideoSelectionOptions pigeonReturn = new VideoSelectionOptions(); - pigeonReturn.setMaxDurationSeconds(maxDurationSeconds); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(1); - toListResult.add(maxDurationSeconds); - return toListResult; - } - - static @NonNull VideoSelectionOptions fromList(@NonNull ArrayList pigeonVar_list) { - VideoSelectionOptions pigeonResult = new VideoSelectionOptions(); - Object maxDurationSeconds = pigeonVar_list.get(0); - pigeonResult.setMaxDurationSeconds((Long) maxDurationSeconds); - return pigeonResult; - } - } - - /** - * Specification for the source of an image or video selection. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class SourceSpecification { - private @NonNull SourceType type; - - public @NonNull SourceType getType() { - return type; - } - - public void setType(@NonNull SourceType setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"type\" is null."); - } - this.type = setterArg; - } - - private @Nullable SourceCamera camera; - - public @Nullable SourceCamera getCamera() { - return camera; - } - - public void setCamera(@Nullable SourceCamera setterArg) { - this.camera = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - SourceSpecification() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - SourceSpecification that = (SourceSpecification) o; - return type.equals(that.type) && Objects.equals(camera, that.camera); - } - - @Override - public int hashCode() { - return Objects.hash(type, camera); - } - - public static final class Builder { - - private @Nullable SourceType type; - - @CanIgnoreReturnValue - public @NonNull Builder setType(@NonNull SourceType setterArg) { - this.type = setterArg; - return this; - } - - private @Nullable SourceCamera camera; - - @CanIgnoreReturnValue - public @NonNull Builder setCamera(@Nullable SourceCamera setterArg) { - this.camera = setterArg; - return this; - } - - public @NonNull SourceSpecification build() { - SourceSpecification pigeonReturn = new SourceSpecification(); - pigeonReturn.setType(type); - pigeonReturn.setCamera(camera); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(2); - toListResult.add(type); - toListResult.add(camera); - return toListResult; - } - - static @NonNull SourceSpecification fromList(@NonNull ArrayList pigeonVar_list) { - SourceSpecification pigeonResult = new SourceSpecification(); - Object type = pigeonVar_list.get(0); - pigeonResult.setType((SourceType) type); - Object camera = pigeonVar_list.get(1); - pigeonResult.setCamera((SourceCamera) camera); - return pigeonResult; - } - } - - /** - * An error that occurred during lost result retrieval. - * - *

The data here maps to the `PlatformException` that will be created from it. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class CacheRetrievalError { - private @NonNull String code; - - public @NonNull String getCode() { - return code; - } - - public void setCode(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"code\" is null."); - } - this.code = setterArg; - } - - private @Nullable String message; - - public @Nullable String getMessage() { - return message; - } - - public void setMessage(@Nullable String setterArg) { - this.message = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - CacheRetrievalError() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - CacheRetrievalError that = (CacheRetrievalError) o; - return code.equals(that.code) && Objects.equals(message, that.message); - } - - @Override - public int hashCode() { - return Objects.hash(code, message); - } - - public static final class Builder { - - private @Nullable String code; - - @CanIgnoreReturnValue - public @NonNull Builder setCode(@NonNull String setterArg) { - this.code = setterArg; - return this; - } - - private @Nullable String message; - - @CanIgnoreReturnValue - public @NonNull Builder setMessage(@Nullable String setterArg) { - this.message = setterArg; - return this; - } - - public @NonNull CacheRetrievalError build() { - CacheRetrievalError pigeonReturn = new CacheRetrievalError(); - pigeonReturn.setCode(code); - pigeonReturn.setMessage(message); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(2); - toListResult.add(code); - toListResult.add(message); - return toListResult; - } - - static @NonNull CacheRetrievalError fromList(@NonNull ArrayList pigeonVar_list) { - CacheRetrievalError pigeonResult = new CacheRetrievalError(); - Object code = pigeonVar_list.get(0); - pigeonResult.setCode((String) code); - Object message = pigeonVar_list.get(1); - pigeonResult.setMessage((String) message); - return pigeonResult; - } - } - - /** - * The result of retrieving cached results from a previous run. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class CacheRetrievalResult { - /** The type of the retrieved data. */ - private @NonNull CacheRetrievalType type; - - public @NonNull CacheRetrievalType getType() { - return type; - } - - public void setType(@NonNull CacheRetrievalType setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"type\" is null."); - } - this.type = setterArg; - } - - /** The error from the last selection, if any. */ - private @Nullable CacheRetrievalError error; - - public @Nullable CacheRetrievalError getError() { - return error; - } - - public void setError(@Nullable CacheRetrievalError setterArg) { - this.error = setterArg; - } - - /** The results from the last selection, if any. */ - private @NonNull List paths; - - public @NonNull List getPaths() { - return paths; - } - - public void setPaths(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"paths\" is null."); - } - this.paths = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - CacheRetrievalResult() {} - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - CacheRetrievalResult that = (CacheRetrievalResult) o; - return type.equals(that.type) - && Objects.equals(error, that.error) - && paths.equals(that.paths); - } - - @Override - public int hashCode() { - return Objects.hash(type, error, paths); - } - - public static final class Builder { - - private @Nullable CacheRetrievalType type; - - @CanIgnoreReturnValue - public @NonNull Builder setType(@NonNull CacheRetrievalType setterArg) { - this.type = setterArg; - return this; - } - - private @Nullable CacheRetrievalError error; - - @CanIgnoreReturnValue - public @NonNull Builder setError(@Nullable CacheRetrievalError setterArg) { - this.error = setterArg; - return this; - } - - private @Nullable List paths; - - @CanIgnoreReturnValue - public @NonNull Builder setPaths(@NonNull List setterArg) { - this.paths = setterArg; - return this; - } - - public @NonNull CacheRetrievalResult build() { - CacheRetrievalResult pigeonReturn = new CacheRetrievalResult(); - pigeonReturn.setType(type); - pigeonReturn.setError(error); - pigeonReturn.setPaths(paths); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList<>(3); - toListResult.add(type); - toListResult.add(error); - toListResult.add(paths); - return toListResult; - } - - static @NonNull CacheRetrievalResult fromList(@NonNull ArrayList pigeonVar_list) { - CacheRetrievalResult pigeonResult = new CacheRetrievalResult(); - Object type = pigeonVar_list.get(0); - pigeonResult.setType((CacheRetrievalType) type); - Object error = pigeonVar_list.get(1); - pigeonResult.setError((CacheRetrievalError) error); - Object paths = pigeonVar_list.get(2); - pigeonResult.setPaths((List) paths); - 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 : SourceCamera.values()[((Long) value).intValue()]; - } - case (byte) 130: - { - Object value = readValue(buffer); - return value == null ? null : SourceType.values()[((Long) value).intValue()]; - } - case (byte) 131: - { - Object value = readValue(buffer); - return value == null ? null : CacheRetrievalType.values()[((Long) value).intValue()]; - } - case (byte) 132: - return GeneralOptions.fromList((ArrayList) readValue(buffer)); - case (byte) 133: - return ImageSelectionOptions.fromList((ArrayList) readValue(buffer)); - case (byte) 134: - return MediaSelectionOptions.fromList((ArrayList) readValue(buffer)); - case (byte) 135: - return VideoSelectionOptions.fromList((ArrayList) readValue(buffer)); - case (byte) 136: - return SourceSpecification.fromList((ArrayList) readValue(buffer)); - case (byte) 137: - return CacheRetrievalError.fromList((ArrayList) readValue(buffer)); - case (byte) 138: - return CacheRetrievalResult.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof SourceCamera) { - stream.write(129); - writeValue(stream, value == null ? null : ((SourceCamera) value).index); - } else if (value instanceof SourceType) { - stream.write(130); - writeValue(stream, value == null ? null : ((SourceType) value).index); - } else if (value instanceof CacheRetrievalType) { - stream.write(131); - writeValue(stream, value == null ? null : ((CacheRetrievalType) value).index); - } else if (value instanceof GeneralOptions) { - stream.write(132); - writeValue(stream, ((GeneralOptions) value).toList()); - } else if (value instanceof ImageSelectionOptions) { - stream.write(133); - writeValue(stream, ((ImageSelectionOptions) value).toList()); - } else if (value instanceof MediaSelectionOptions) { - stream.write(134); - writeValue(stream, ((MediaSelectionOptions) value).toList()); - } else if (value instanceof VideoSelectionOptions) { - stream.write(135); - writeValue(stream, ((VideoSelectionOptions) value).toList()); - } else if (value instanceof SourceSpecification) { - stream.write(136); - writeValue(stream, ((SourceSpecification) value).toList()); - } else if (value instanceof CacheRetrievalError) { - stream.write(137); - writeValue(stream, ((CacheRetrievalError) value).toList()); - } else if (value instanceof CacheRetrievalResult) { - stream.write(138); - writeValue(stream, ((CacheRetrievalResult) 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); - } - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ - public interface ImagePickerApi { - /** Selects images and returns their paths. */ - void pickImages( - @NonNull SourceSpecification source, - @NonNull ImageSelectionOptions options, - @NonNull GeneralOptions generalOptions, - @NonNull Result> result); - /** Selects video and returns their paths. */ - void pickVideos( - @NonNull SourceSpecification source, - @NonNull VideoSelectionOptions options, - @NonNull GeneralOptions generalOptions, - @NonNull Result> result); - /** Selects images and videos and returns their paths. */ - void pickMedia( - @NonNull MediaSelectionOptions mediaSelectionOptions, - @NonNull GeneralOptions generalOptions, - @NonNull Result> result); - /** Returns results from a previous app session, if any. */ - @Nullable - CacheRetrievalResult retrieveLostResults(); - - /** The codec used by ImagePickerApi. */ - static @NonNull MessageCodec getCodec() { - return PigeonCodec.INSTANCE; - } - /** Sets up an instance of `ImagePickerApi` to handle messages through the `binaryMessenger`. */ - static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable ImagePickerApi api) { - setUp(binaryMessenger, "", api); - } - - static void setUp( - @NonNull BinaryMessenger binaryMessenger, - @NonNull String messageChannelSuffix, - @Nullable ImagePickerApi api) { - messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; - BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickImages" - + messageChannelSuffix, - getCodec(), - taskQueue); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - SourceSpecification sourceArg = (SourceSpecification) args.get(0); - ImageSelectionOptions optionsArg = (ImageSelectionOptions) args.get(1); - GeneralOptions generalOptionsArg = (GeneralOptions) args.get(2); - 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.pickImages(sourceArg, optionsArg, generalOptionsArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickVideos" - + messageChannelSuffix, - getCodec(), - taskQueue); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - SourceSpecification sourceArg = (SourceSpecification) args.get(0); - VideoSelectionOptions optionsArg = (VideoSelectionOptions) args.get(1); - GeneralOptions generalOptionsArg = (GeneralOptions) args.get(2); - 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.pickVideos(sourceArg, optionsArg, generalOptionsArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickMedia" - + messageChannelSuffix, - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - MediaSelectionOptions mediaSelectionOptionsArg = - (MediaSelectionOptions) args.get(0); - GeneralOptions generalOptionsArg = (GeneralOptions) 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.pickMedia(mediaSelectionOptionsArg, generalOptionsArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.image_picker_android.ImagePickerApi.retrieveLostResults" - + messageChannelSuffix, - getCodec(), - taskQueue); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList<>(); - try { - CacheRetrievalResult output = api.retrieveLostResults(); - wrapped.add(0, output); - } catch (Throwable exception) { - wrapped = wrapError(exception); - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - } - } -} diff --git a/packages/image_picker/image_picker_android/android/src/main/kotlin/io/flutter/plugins/imagepicker/Messages.kt b/packages/image_picker/image_picker_android/android/src/main/kotlin/io/flutter/plugins/imagepicker/Messages.kt new file mode 100644 index 00000000000..db7d98bdec8 --- /dev/null +++ b/packages/image_picker/image_picker_android/android/src/main/kotlin/io/flutter/plugins/imagepicker/Messages.kt @@ -0,0 +1,763 @@ +// 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 io.flutter.plugins.imagepicker + +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 MessagesPigeonUtils { + + 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 SourceCamera(val raw: Int) { + REAR(0), + FRONT(1); + + companion object { + fun ofRaw(raw: Int): SourceCamera? { + return values().firstOrNull { it.raw == raw } + } + } +} + +enum class SourceType(val raw: Int) { + CAMERA(0), + GALLERY(1); + + companion object { + fun ofRaw(raw: Int): SourceType? { + return values().firstOrNull { it.raw == raw } + } + } +} + +enum class CacheRetrievalType(val raw: Int) { + IMAGE(0), + VIDEO(1); + + companion object { + fun ofRaw(raw: Int): CacheRetrievalType? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class GeneralOptions( + val allowMultiple: Boolean, + val usePhotoPicker: Boolean, + val limit: Long? = null +) { + companion object { + fun fromList(pigeonVar_list: List): GeneralOptions { + val allowMultiple = pigeonVar_list[0] as Boolean + val usePhotoPicker = pigeonVar_list[1] as Boolean + val limit = pigeonVar_list[2] as Long? + return GeneralOptions(allowMultiple, usePhotoPicker, limit) + } + } + + fun toList(): List { + return listOf( + allowMultiple, + usePhotoPicker, + limit, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as GeneralOptions + return MessagesPigeonUtils.deepEquals(this.allowMultiple, other.allowMultiple) && + MessagesPigeonUtils.deepEquals(this.usePhotoPicker, other.usePhotoPicker) && + MessagesPigeonUtils.deepEquals(this.limit, other.limit) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.allowMultiple) + result = 31 * result + MessagesPigeonUtils.deepHash(this.usePhotoPicker) + result = 31 * result + MessagesPigeonUtils.deepHash(this.limit) + return result + } +} + +/** + * Options for image selection and output. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class ImageSelectionOptions( + /** If set, the max width that the image should be resized to fit in. */ + val maxWidth: Double? = null, + /** If set, the max height that the image should be resized to fit in. */ + val maxHeight: Double? = null, + /** + * The quality of the output image, from 0-100. + * + * 100 indicates original quality. + */ + val quality: Long +) { + companion object { + fun fromList(pigeonVar_list: List): ImageSelectionOptions { + val maxWidth = pigeonVar_list[0] as Double? + val maxHeight = pigeonVar_list[1] as Double? + val quality = pigeonVar_list[2] as Long + return ImageSelectionOptions(maxWidth, maxHeight, quality) + } + } + + fun toList(): List { + return listOf( + maxWidth, + maxHeight, + quality, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as ImageSelectionOptions + return MessagesPigeonUtils.deepEquals(this.maxWidth, other.maxWidth) && + MessagesPigeonUtils.deepEquals(this.maxHeight, other.maxHeight) && + MessagesPigeonUtils.deepEquals(this.quality, other.quality) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.maxWidth) + result = 31 * result + MessagesPigeonUtils.deepHash(this.maxHeight) + result = 31 * result + MessagesPigeonUtils.deepHash(this.quality) + return result + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class MediaSelectionOptions(val imageSelectionOptions: ImageSelectionOptions) { + companion object { + fun fromList(pigeonVar_list: List): MediaSelectionOptions { + val imageSelectionOptions = pigeonVar_list[0] as ImageSelectionOptions + return MediaSelectionOptions(imageSelectionOptions) + } + } + + fun toList(): List { + return listOf( + imageSelectionOptions, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as MediaSelectionOptions + return MessagesPigeonUtils.deepEquals(this.imageSelectionOptions, other.imageSelectionOptions) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.imageSelectionOptions) + return result + } +} + +/** + * Options for image selection and output. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class VideoSelectionOptions( + /** The maximum desired length for the video, in seconds. */ + val maxDurationSeconds: Long? = null +) { + companion object { + fun fromList(pigeonVar_list: List): VideoSelectionOptions { + val maxDurationSeconds = pigeonVar_list[0] as Long? + return VideoSelectionOptions(maxDurationSeconds) + } + } + + fun toList(): List { + return listOf( + maxDurationSeconds, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as VideoSelectionOptions + return MessagesPigeonUtils.deepEquals(this.maxDurationSeconds, other.maxDurationSeconds) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.maxDurationSeconds) + return result + } +} + +/** + * Specification for the source of an image or video selection. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class SourceSpecification(val type: SourceType, val camera: SourceCamera? = null) { + companion object { + fun fromList(pigeonVar_list: List): SourceSpecification { + val type = pigeonVar_list[0] as SourceType + val camera = pigeonVar_list[1] as SourceCamera? + return SourceSpecification(type, camera) + } + } + + fun toList(): List { + return listOf( + type, + camera, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as SourceSpecification + return MessagesPigeonUtils.deepEquals(this.type, other.type) && + MessagesPigeonUtils.deepEquals(this.camera, other.camera) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.type) + result = 31 * result + MessagesPigeonUtils.deepHash(this.camera) + return result + } +} + +/** + * An error that occurred during lost result retrieval. + * + * The data here maps to the `PlatformException` that will be created from it. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class CacheRetrievalError(val code: String, val message: String? = null) { + companion object { + fun fromList(pigeonVar_list: List): CacheRetrievalError { + val code = pigeonVar_list[0] as String + val message = pigeonVar_list[1] as String? + return CacheRetrievalError(code, message) + } + } + + fun toList(): List { + return listOf( + code, + message, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as CacheRetrievalError + return MessagesPigeonUtils.deepEquals(this.code, other.code) && + MessagesPigeonUtils.deepEquals(this.message, other.message) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.code) + result = 31 * result + MessagesPigeonUtils.deepHash(this.message) + return result + } +} + +/** + * The result of retrieving cached results from a previous run. + * + * Generated class from Pigeon that represents data sent in messages. + */ +data class CacheRetrievalResult( + /** The type of the retrieved data. */ + val type: CacheRetrievalType, + /** The error from the last selection, if any. */ + val error: CacheRetrievalError? = null, + /** The results from the last selection, if any. */ + val paths: List +) { + companion object { + fun fromList(pigeonVar_list: List): CacheRetrievalResult { + val type = pigeonVar_list[0] as CacheRetrievalType + val error = pigeonVar_list[1] as CacheRetrievalError? + val paths = pigeonVar_list[2] as List + return CacheRetrievalResult(type, error, paths) + } + } + + fun toList(): List { + return listOf( + type, + error, + paths, + ) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as CacheRetrievalResult + return MessagesPigeonUtils.deepEquals(this.type, other.type) && + MessagesPigeonUtils.deepEquals(this.error, other.error) && + MessagesPigeonUtils.deepEquals(this.paths, other.paths) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + MessagesPigeonUtils.deepHash(this.type) + result = 31 * result + MessagesPigeonUtils.deepHash(this.error) + result = 31 * result + MessagesPigeonUtils.deepHash(this.paths) + return result + } +} + +private open class MessagesPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as Long?)?.let { SourceCamera.ofRaw(it.toInt()) } + } + 130.toByte() -> { + return (readValue(buffer) as Long?)?.let { SourceType.ofRaw(it.toInt()) } + } + 131.toByte() -> { + return (readValue(buffer) as Long?)?.let { CacheRetrievalType.ofRaw(it.toInt()) } + } + 132.toByte() -> { + return (readValue(buffer) as? List)?.let { GeneralOptions.fromList(it) } + } + 133.toByte() -> { + return (readValue(buffer) as? List)?.let { ImageSelectionOptions.fromList(it) } + } + 134.toByte() -> { + return (readValue(buffer) as? List)?.let { MediaSelectionOptions.fromList(it) } + } + 135.toByte() -> { + return (readValue(buffer) as? List)?.let { VideoSelectionOptions.fromList(it) } + } + 136.toByte() -> { + return (readValue(buffer) as? List)?.let { SourceSpecification.fromList(it) } + } + 137.toByte() -> { + return (readValue(buffer) as? List)?.let { CacheRetrievalError.fromList(it) } + } + 138.toByte() -> { + return (readValue(buffer) as? List)?.let { CacheRetrievalResult.fromList(it) } + } + else -> super.readValueOfType(type, buffer) + } + } + + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is SourceCamera -> { + stream.write(129) + writeValue(stream, value.raw.toLong()) + } + is SourceType -> { + stream.write(130) + writeValue(stream, value.raw.toLong()) + } + is CacheRetrievalType -> { + stream.write(131) + writeValue(stream, value.raw.toLong()) + } + is GeneralOptions -> { + stream.write(132) + writeValue(stream, value.toList()) + } + is ImageSelectionOptions -> { + stream.write(133) + writeValue(stream, value.toList()) + } + is MediaSelectionOptions -> { + stream.write(134) + writeValue(stream, value.toList()) + } + is VideoSelectionOptions -> { + stream.write(135) + writeValue(stream, value.toList()) + } + is SourceSpecification -> { + stream.write(136) + writeValue(stream, value.toList()) + } + is CacheRetrievalError -> { + stream.write(137) + writeValue(stream, value.toList()) + } + is CacheRetrievalResult -> { + stream.write(138) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface ImagePickerApi { + /** Selects images and returns their paths. */ + fun pickImages( + source: SourceSpecification, + options: ImageSelectionOptions, + generalOptions: GeneralOptions, + callback: (Result>) -> Unit + ) + /** Selects video and returns their paths. */ + fun pickVideos( + source: SourceSpecification, + options: VideoSelectionOptions, + generalOptions: GeneralOptions, + callback: (Result>) -> Unit + ) + /** Selects images and videos and returns their paths. */ + fun pickMedia( + mediaSelectionOptions: MediaSelectionOptions, + generalOptions: GeneralOptions, + callback: (Result>) -> Unit + ) + /** Returns results from a previous app session, if any. */ + fun retrieveLostResults(): CacheRetrievalResult? + + companion object { + /** The codec used by ImagePickerApi. */ + val codec: MessageCodec by lazy { MessagesPigeonCodec() } + /** Sets up an instance of `ImagePickerApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp( + binaryMessenger: BinaryMessenger, + api: ImagePickerApi?, + messageChannelSuffix: String = "" + ) { + val separatedMessageChannelSuffix = + if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickImages$separatedMessageChannelSuffix", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val sourceArg = args[0] as SourceSpecification + val optionsArg = args[1] as ImageSelectionOptions + val generalOptionsArg = args[2] as GeneralOptions + api.pickImages(sourceArg, optionsArg, generalOptionsArg) { result: Result> + -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickVideos$separatedMessageChannelSuffix", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val sourceArg = args[0] as SourceSpecification + val optionsArg = args[1] as VideoSelectionOptions + val generalOptionsArg = args[2] as GeneralOptions + api.pickVideos(sourceArg, optionsArg, generalOptionsArg) { result: Result> + -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickMedia$separatedMessageChannelSuffix", + codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val mediaSelectionOptionsArg = args[0] as MediaSelectionOptions + val generalOptionsArg = args[1] as GeneralOptions + api.pickMedia(mediaSelectionOptionsArg, generalOptionsArg) { + result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = + BasicMessageChannel( + binaryMessenger, + "dev.flutter.pigeon.image_picker_android.ImagePickerApi.retrieveLostResults$separatedMessageChannelSuffix", + codec, + taskQueue) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = + try { + listOf(api.retrieveLostResults()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/packages/image_picker/image_picker_android/lib/src/messages.g.dart b/packages/image_picker/image_picker_android/lib/src/messages.g.dart index 89aea6de03b..3952984b7eb 100644 --- a/packages/image_picker/image_picker_android/lib/src/messages.g.dart +++ b/packages/image_picker/image_picker_android/lib/src/messages.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 SourceCamera { rear, front } enum SourceType { camera, gallery } @@ -81,12 +145,14 @@ class GeneralOptions { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(allowMultiple, other.allowMultiple) && + _deepEquals(usePhotoPicker, other.usePhotoPicker) && + _deepEquals(limit, other.limit); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } /// Options for image selection and output. @@ -130,12 +196,14 @@ class ImageSelectionOptions { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(maxWidth, other.maxWidth) && + _deepEquals(maxHeight, other.maxHeight) && + _deepEquals(quality, other.quality); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } class MediaSelectionOptions { @@ -167,12 +235,12 @@ class MediaSelectionOptions { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(imageSelectionOptions, other.imageSelectionOptions); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } /// Options for image selection and output. @@ -204,12 +272,12 @@ class VideoSelectionOptions { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(maxDurationSeconds, other.maxDurationSeconds); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } /// Specification for the source of an image or video selection. @@ -245,12 +313,12 @@ class SourceSpecification { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(type, other.type) && _deepEquals(camera, other.camera); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => _deepHash([runtimeType, ..._toList()]); } /// An error that occurred during lost result retrieval. @@ -288,12 +356,12 @@ class CacheRetrievalError { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(code, other.code) && _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()]); } /// The result of retrieving cached results from a previous run. @@ -326,7 +394,7 @@ class CacheRetrievalResult { return CacheRetrievalResult( type: result[0]! as CacheRetrievalType, error: result[1] as CacheRetrievalError?, - paths: (result[2] as List?)!.cast(), + paths: (result[2]! as List).cast(), ); } @@ -339,12 +407,14 @@ class CacheRetrievalResult { if (identical(this, other)) { return true; } - return _deepEquals(encode(), other.encode()); + return _deepEquals(type, other.type) && + _deepEquals(error, other.error) && + _deepEquals(paths, other.paths); } @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 { @@ -393,13 +463,13 @@ 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 : SourceCamera.values[value]; case 130: - final int? value = readValue(buffer) as int?; + final value = readValue(buffer) as int?; return value == null ? null : SourceType.values[value]; case 131: - final int? value = readValue(buffer) as int?; + final value = readValue(buffer) as int?; return value == null ? null : CacheRetrievalType.values[value]; case 132: return GeneralOptions.decode(readValue(buffer)!); @@ -444,35 +514,24 @@ class ImagePickerApi { ImageSelectionOptions options, GeneralOptions generalOptions, ) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickImages$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( [source, options, generalOptions], ); - 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(); } /// Selects video and returns their paths. @@ -481,35 +540,24 @@ class ImagePickerApi { VideoSelectionOptions options, GeneralOptions generalOptions, ) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickVideos$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( [source, options, generalOptions], ); - 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(); } /// Selects images and videos and returns their paths. @@ -517,60 +565,43 @@ class ImagePickerApi { MediaSelectionOptions mediaSelectionOptions, GeneralOptions generalOptions, ) async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.image_picker_android.ImagePickerApi.pickMedia$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( [mediaSelectionOptions, generalOptions], ); - 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(); } /// Returns results from a previous app session, if any. Future retrieveLostResults() async { - final String pigeonVar_channelName = + final pigeonVar_channelName = 'dev.flutter.pigeon.image_picker_android.ImagePickerApi.retrieveLostResults$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(null); - 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 CacheRetrievalResult?); - } + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: true, + ); + return pigeonVar_replyValue as CacheRetrievalResult?; } } diff --git a/packages/image_picker/image_picker_android/pigeons/messages.dart b/packages/image_picker/image_picker_android/pigeons/messages.dart index 940e4650eab..502cd575e06 100644 --- a/packages/image_picker/image_picker_android/pigeons/messages.dart +++ b/packages/image_picker/image_picker_android/pigeons/messages.dart @@ -7,9 +7,9 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( dartOut: 'lib/src/messages.g.dart', - javaOut: - 'android/src/main/java/io/flutter/plugins/imagepicker/Messages.java', - javaOptions: JavaOptions(package: 'io.flutter.plugins.imagepicker'), + kotlinOut: + 'android/src/main/kotlin/io/flutter/plugins/imagepicker/Messages.kt', + kotlinOptions: KotlinOptions(package: 'io.flutter.plugins.imagepicker'), copyrightHeader: 'pigeons/copyright.txt', ), ) diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index a2ed7681559..b23de7a0c23 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -27,7 +27,7 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.4.4 - pigeon: ^26.1.0 + pigeon: ^26.3.4 topics: - camera From b89daef1c05bb318ac875ad7d3433ba1a66898a6 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 10 Apr 2026 14:24:20 -0400 Subject: [PATCH 3/9] Add utils --- .../plugins/imagepicker/ResultUtils.kt | 13 +++++++ .../plugins/imagepicker/TestResultUtils.kt | 35 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 packages/image_picker/image_picker_android/android/src/main/kotlin/io/flutter/plugins/imagepicker/ResultUtils.kt create mode 100644 packages/image_picker/image_picker_android/android/src/test/kotlin/io/flutter/plugins/imagepicker/TestResultUtils.kt diff --git a/packages/image_picker/image_picker_android/android/src/main/kotlin/io/flutter/plugins/imagepicker/ResultUtils.kt b/packages/image_picker/image_picker_android/android/src/main/kotlin/io/flutter/plugins/imagepicker/ResultUtils.kt new file mode 100644 index 00000000000..58a01fa8a1f --- /dev/null +++ b/packages/image_picker/image_picker_android/android/src/main/kotlin/io/flutter/plugins/imagepicker/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 io.flutter.plugins.imagepicker + +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/image_picker/image_picker_android/android/src/test/kotlin/io/flutter/plugins/imagepicker/TestResultUtils.kt b/packages/image_picker/image_picker_android/android/src/test/kotlin/io/flutter/plugins/imagepicker/TestResultUtils.kt new file mode 100644 index 00000000000..9615d270ecf --- /dev/null +++ b/packages/image_picker/image_picker_android/android/src/test/kotlin/io/flutter/plugins/imagepicker/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.imagepicker + +/** 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 + } +} From cfa7d2c7ea3342b0ec2f9a60e30efe39eea69ada Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 10 Apr 2026 14:43:31 -0400 Subject: [PATCH 4/9] Manual non-test fixes part 1: everything but Result --- .../plugins/imagepicker/ImagePickerCache.java | 19 +++-- .../imagepicker/ImagePickerDelegate.java | 77 +++++++++---------- .../imagepicker/ImagePickerPlugin.java | 28 +++---- .../plugins/imagepicker/ImagePickerUtils.java | 2 +- 4 files changed, 54 insertions(+), 72 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerCache.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerCache.java index 848d5bbea4d..6e908f8a5df 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerCache.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerCache.java @@ -75,7 +75,7 @@ private void setType(String type) { prefs.edit().putString(SHARED_PREFERENCE_TYPE_KEY, type).apply(); } - void saveDimensionWithOutputOptions(Messages.ImageSelectionOptions options) { + void saveDimensionWithOutputOptions(ImageSelectionOptions options) { final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); @@ -87,7 +87,7 @@ void saveDimensionWithOutputOptions(Messages.ImageSelectionOptions options) { editor.putLong( SHARED_PREFERENCE_MAX_HEIGHT_KEY, Double.doubleToRawLongBits(options.getMaxHeight())); } - editor.putInt(SHARED_PREFERENCE_IMAGE_QUALITY_KEY, options.getQuality().intValue()); + editor.putInt(SHARED_PREFERENCE_IMAGE_QUALITY_KEY, (int) options.getQuality()); editor.apply(); } @@ -146,13 +146,12 @@ Map getCacheMap() { } if (prefs.contains(SHARED_PREFERENCE_ERROR_CODE_KEY)) { - final Messages.CacheRetrievalError.Builder error = new Messages.CacheRetrievalError.Builder(); - error.setCode(prefs.getString(SHARED_PREFERENCE_ERROR_CODE_KEY, "")); + final CacheRetrievalError error = + new CacheRetrievalError( + prefs.getString(SHARED_PREFERENCE_ERROR_CODE_KEY, ""), + prefs.getString(SHARED_PREFERENCE_ERROR_MESSAGE_KEY, null)); hasData = true; - if (prefs.contains(SHARED_PREFERENCE_ERROR_MESSAGE_KEY)) { - error.setMessage(prefs.getString(SHARED_PREFERENCE_ERROR_MESSAGE_KEY, "")); - } - resultMap.put(MAP_KEY_ERROR, error.build()); + resultMap.put(MAP_KEY_ERROR, error); } if (hasData) { @@ -161,8 +160,8 @@ Map getCacheMap() { resultMap.put( MAP_KEY_TYPE, typeValue.equals(MAP_TYPE_VALUE_VIDEO) - ? Messages.CacheRetrievalType.VIDEO - : Messages.CacheRetrievalType.IMAGE); + ? CacheRetrievalType.VIDEO + : CacheRetrievalType.IMAGE); } if (prefs.contains(SHARED_PREFERENCE_MAX_WIDTH_KEY)) { final long maxWidthValue = prefs.getLong(SHARED_PREFERENCE_MAX_WIDTH_KEY, 0); diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 043546de2b3..7bc96caf56c 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -24,9 +24,6 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.FileProvider; import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugins.imagepicker.Messages.FlutterError; -import io.flutter.plugins.imagepicker.Messages.ImageSelectionOptions; -import io.flutter.plugins.imagepicker.Messages.VideoSelectionOptions; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -96,12 +93,12 @@ public enum CameraDevice { private static class PendingCallState { public final @Nullable ImageSelectionOptions imageOptions; public final @Nullable VideoSelectionOptions videoOptions; - public final @NonNull Messages.Result> result; + public final @NonNull Result> result; PendingCallState( @Nullable ImageSelectionOptions imageOptions, @Nullable VideoSelectionOptions videoOptions, - @NonNull Messages.Result> result) { + @NonNull Result> result) { this.imageOptions = imageOptions; this.videoOptions = videoOptions; this.result = result; @@ -198,7 +195,7 @@ public void getFullImagePath(final Uri imageUri, final OnPathReadyListener liste final @NonNull ImageResizer imageResizer, final @Nullable ImageSelectionOptions pendingImageOptions, final @Nullable VideoSelectionOptions pendingVideoOptions, - final @Nullable Messages.Result> result, + final @Nullable Result> result, final @NonNull ImagePickerCache cache, final PermissionManager permissionManager, final FileUriResolver fileUriResolver, @@ -247,45 +244,41 @@ void saveStateBeforeResult() { } @Nullable - Messages.CacheRetrievalResult retrieveLostImage() { + CacheRetrievalResult retrieveLostImage() { Map cacheMap = cache.getCacheMap(); if (cacheMap.isEmpty()) { return null; } - Messages.CacheRetrievalResult.Builder result = new Messages.CacheRetrievalResult.Builder(); - - Messages.CacheRetrievalType type = - (Messages.CacheRetrievalType) cacheMap.get(ImagePickerCache.MAP_KEY_TYPE); - if (type != null) { - result.setType(type); - } - result.setError((Messages.CacheRetrievalError) cacheMap.get(ImagePickerCache.MAP_KEY_ERROR)); + CacheRetrievalType type = (CacheRetrievalType) cacheMap.get(ImagePickerCache.MAP_KEY_TYPE); @SuppressWarnings("unchecked") ArrayList pathList = (ArrayList) cacheMap.get(ImagePickerCache.MAP_KEY_PATH_LIST); - if (pathList != null) { - ArrayList newPathList = new ArrayList<>(); - for (String path : pathList) { - Double maxWidth = (Double) cacheMap.get(ImagePickerCache.MAP_KEY_MAX_WIDTH); - Double maxHeight = (Double) cacheMap.get(ImagePickerCache.MAP_KEY_MAX_HEIGHT); - Integer boxedImageQuality = (Integer) cacheMap.get(ImagePickerCache.MAP_KEY_IMAGE_QUALITY); - int imageQuality = boxedImageQuality == null ? 100 : boxedImageQuality; - - newPathList.add(imageResizer.resizeImageIfNeeded(path, maxWidth, maxHeight, imageQuality)); - } - result.setPaths(newPathList); + if (type == null || pathList == null) { + // This should never happen, so if it does the cache is no longer valid. + cache.clear(); + return null; + } + ArrayList newPathList = new ArrayList<>(); + for (String path : pathList) { + Double maxWidth = (Double) cacheMap.get(ImagePickerCache.MAP_KEY_MAX_WIDTH); + Double maxHeight = (Double) cacheMap.get(ImagePickerCache.MAP_KEY_MAX_HEIGHT); + Integer boxedImageQuality = (Integer) cacheMap.get(ImagePickerCache.MAP_KEY_IMAGE_QUALITY); + int imageQuality = boxedImageQuality == null ? 100 : boxedImageQuality; + + newPathList.add(imageResizer.resizeImageIfNeeded(path, maxWidth, maxHeight, imageQuality)); } cache.clear(); - return result.build(); + return new CacheRetrievalResult( + type, (CacheRetrievalError) cacheMap.get(ImagePickerCache.MAP_KEY_ERROR), newPathList); } public void chooseMediaFromGallery( - @NonNull Messages.MediaSelectionOptions options, - @NonNull Messages.GeneralOptions generalOptions, - @NonNull Messages.Result> result) { + @NonNull MediaSelectionOptions options, + @NonNull GeneralOptions generalOptions, + @NonNull Result> result) { if (!setPendingOptionsAndResult(options.getImageSelectionOptions(), null, result)) { finishWithAlreadyActiveError(result); return; @@ -294,7 +287,7 @@ public void chooseMediaFromGallery( launchPickMediaFromGalleryIntent(generalOptions); } - private void launchPickMediaFromGalleryIntent(Messages.GeneralOptions generalOptions) { + private void launchPickMediaFromGalleryIntent(GeneralOptions generalOptions) { Intent pickMediaIntent; if (generalOptions.getUsePhotoPicker()) { if (generalOptions.getAllowMultiple()) { @@ -331,7 +324,7 @@ private void launchPickMediaFromGalleryIntent(Messages.GeneralOptions generalOpt public void chooseVideoFromGallery( @NonNull VideoSelectionOptions options, boolean usePhotoPicker, - @NonNull Messages.Result> result) { + @NonNull Result> result) { if (!setPendingOptionsAndResult(null, options, result)) { finishWithAlreadyActiveError(result); return; @@ -359,7 +352,7 @@ private void launchPickVideoFromGalleryIntent(Boolean usePhotoPicker) { } public void takeVideoWithCamera( - @NonNull VideoSelectionOptions options, @NonNull Messages.Result> result) { + @NonNull VideoSelectionOptions options, @NonNull Result> result) { if (!setPendingOptionsAndResult(null, options, result)) { finishWithAlreadyActiveError(result); return; @@ -417,7 +410,7 @@ private void launchTakeVideoWithCameraIntent() { public void chooseImageFromGallery( @NonNull ImageSelectionOptions options, boolean usePhotoPicker, - @NonNull Messages.Result> result) { + @NonNull Result> result) { if (!setPendingOptionsAndResult(options, null, result)) { finishWithAlreadyActiveError(result); return; @@ -430,7 +423,7 @@ public void chooseMultiImageFromGallery( @NonNull ImageSelectionOptions options, boolean usePhotoPicker, int limit, - @NonNull Messages.Result> result) { + @NonNull Result> result) { if (!setPendingOptionsAndResult(options, null, result)) { finishWithAlreadyActiveError(result); return; @@ -479,7 +472,7 @@ public void chooseMultiVideoFromGallery( @NonNull VideoSelectionOptions options, boolean usePhotoPicker, int limit, - @NonNull Messages.Result> result) { + @NonNull Result> result) { if (!setPendingOptionsAndResult(null, options, result)) { finishWithAlreadyActiveError(result); return; @@ -508,7 +501,7 @@ private void launchMultiPickVideoFromGalleryIntent(Boolean usePhotoPicker, int l } public void takeImageWithCamera( - @NonNull ImageSelectionOptions options, @NonNull Messages.Result> result) { + @NonNull ImageSelectionOptions options, @NonNull Result> result) { if (!setPendingOptionsAndResult(options, null, result)) { finishWithAlreadyActiveError(result); return; @@ -909,7 +902,7 @@ private void handleMediaResult(@NonNull ArrayList paths) { private boolean setPendingOptionsAndResult( @Nullable ImageSelectionOptions imageOptions, @Nullable VideoSelectionOptions videoOptions, - @NonNull Messages.Result> result) { + @NonNull Result> result) { synchronized (pendingCallStateLock) { if (pendingCallState != null) { return false; @@ -933,7 +926,7 @@ private void finishWithSuccess(@Nullable String imagePath) { pathList.add(imagePath); } - Messages.Result> localResult = null; + Result> localResult = null; synchronized (pendingCallStateLock) { if (pendingCallState != null) { localResult = pendingCallState.result; @@ -952,7 +945,7 @@ private void finishWithSuccess(@Nullable String imagePath) { } private void finishWithListSuccess(ArrayList imagePaths) { - Messages.Result> localResult = null; + Result> localResult = null; synchronized (pendingCallStateLock) { if (pendingCallState != null) { localResult = pendingCallState.result; @@ -967,12 +960,12 @@ private void finishWithListSuccess(ArrayList imagePaths) { } } - private void finishWithAlreadyActiveError(Messages.Result> result) { + private void finishWithAlreadyActiveError(Result> result) { result.error(new FlutterError("already_active", "Image picker is already active", null)); } private void finishWithError(String errorCode, String errorMessage) { - Messages.Result> localResult = null; + Result> localResult = null; synchronized (pendingCallStateLock) { if (pendingCallState != null) { localResult = pendingCallState.result; diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java index 41a5c8fcfb9..87942e2fe38 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java @@ -18,16 +18,6 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugins.imagepicker.Messages.CacheRetrievalResult; -import io.flutter.plugins.imagepicker.Messages.FlutterError; -import io.flutter.plugins.imagepicker.Messages.GeneralOptions; -import io.flutter.plugins.imagepicker.Messages.ImagePickerApi; -import io.flutter.plugins.imagepicker.Messages.ImageSelectionOptions; -import io.flutter.plugins.imagepicker.Messages.MediaSelectionOptions; -import io.flutter.plugins.imagepicker.Messages.Result; -import io.flutter.plugins.imagepicker.Messages.SourceCamera; -import io.flutter.plugins.imagepicker.Messages.SourceSpecification; -import io.flutter.plugins.imagepicker.Messages.VideoSelectionOptions; import java.util.List; @SuppressWarnings("deprecation") @@ -64,22 +54,22 @@ public void onDestroy(@NonNull LifecycleOwner owner) { } @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} + public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) {} @Override - public void onActivityStarted(Activity activity) {} + public void onActivityStarted(@NonNull Activity activity) {} @Override - public void onActivityResumed(Activity activity) {} + public void onActivityResumed(@NonNull Activity activity) {} @Override - public void onActivityPaused(Activity activity) {} + public void onActivityPaused(@NonNull Activity activity) {} @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {} @Override - public void onActivityDestroyed(Activity activity) { + public void onActivityDestroyed(@NonNull Activity activity) { if (thisActivity == activity && activity.getApplicationContext() != null) { ((Application) activity.getApplicationContext()) .unregisterActivityLifecycleCallbacks( @@ -88,7 +78,7 @@ public void onActivityDestroyed(Activity activity) { } @Override - public void onActivityStopped(Activity activity) { + public void onActivityStopped(@NonNull Activity activity) { if (thisActivity == activity) { activityState.getDelegate().saveStateBeforeResult(); } @@ -123,7 +113,7 @@ private class ActivityState { this.messenger = messenger; delegate = constructDelegate(activity); - ImagePickerApi.setUp(messenger, handler); + ImagePickerApi.Companion.setUp(messenger, handler); observer = new LifeCycleObserver(activity); // V2 embedding setup for activity listeners. @@ -151,7 +141,7 @@ void release() { lifecycle = null; } - ImagePickerApi.setUp(messenger, null); + ImagePickerApi.Companion.setUp(messenger, null); if (application != null) { application.unregisterActivityLifecycleCallbacks(observer); diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java index 1499a860d7d..8fbf44fa674 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java @@ -76,7 +76,7 @@ static int getMaxItems() { } } - static int getLimitFromOption(Messages.GeneralOptions generalOptions) { + static int getLimitFromOption(GeneralOptions generalOptions) { Long limit = generalOptions.getLimit(); int effectiveLimit = getMaxItems(); From 11f9366154e940060f61e0618feb0c664b487d8f Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 13 Apr 2026 13:45:20 -0400 Subject: [PATCH 5/9] Manual non-test fixes part 2: Result --- .../imagepicker/ImagePickerDelegate.java | 134 ++++++++++++------ .../imagepicker/ImagePickerPlugin.java | 42 ++++-- 2 files changed, 119 insertions(+), 57 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 7bc96caf56c..6e3e2aefaa9 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -32,6 +32,10 @@ import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import kotlin.Result; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; +import org.jetbrains.annotations.NotNull; /** * A delegate class doing the heavy lifting for the plugin. @@ -93,15 +97,21 @@ public enum CameraDevice { private static class PendingCallState { public final @Nullable ImageSelectionOptions imageOptions; public final @Nullable VideoSelectionOptions videoOptions; - public final @NonNull Result> result; + public final @NonNull Function1< + ? super @NotNull Result>, @NotNull Unit> + callback; PendingCallState( @Nullable ImageSelectionOptions imageOptions, @Nullable VideoSelectionOptions videoOptions, - @NonNull Result> result) { + @NonNull + Function1< + ? super @NotNull Result>, @NotNull + Unit> + callback) { this.imageOptions = imageOptions; this.videoOptions = videoOptions; - this.result = result; + this.callback = callback; } } @@ -195,7 +205,9 @@ public void getFullImagePath(final Uri imageUri, final OnPathReadyListener liste final @NonNull ImageResizer imageResizer, final @Nullable ImageSelectionOptions pendingImageOptions, final @Nullable VideoSelectionOptions pendingVideoOptions, - final @Nullable Result> result, + final @Nullable Function1< + ? super @NotNull Result>, @NotNull Unit> + callback, final @NonNull ImagePickerCache cache, final PermissionManager permissionManager, final FileUriResolver fileUriResolver, @@ -204,9 +216,9 @@ public void getFullImagePath(final Uri imageUri, final OnPathReadyListener liste this.activity = activity; this.imageResizer = imageResizer; this.fileProviderName = activity.getPackageName() + ".flutter.image_provider"; - if (result != null) { + if (callback != null) { this.pendingCallState = - new PendingCallState(pendingImageOptions, pendingVideoOptions, result); + new PendingCallState(pendingImageOptions, pendingVideoOptions, callback); } this.permissionManager = permissionManager; this.fileUriResolver = fileUriResolver; @@ -278,9 +290,12 @@ CacheRetrievalResult retrieveLostImage() { public void chooseMediaFromGallery( @NonNull MediaSelectionOptions options, @NonNull GeneralOptions generalOptions, - @NonNull Result> result) { - if (!setPendingOptionsAndResult(options.getImageSelectionOptions(), null, result)) { - finishWithAlreadyActiveError(result); + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { + if (!setPendingOptionsAndResult(options.getImageSelectionOptions(), null, callback)) { + finishWithAlreadyActiveError(callback); return; } @@ -324,9 +339,12 @@ private void launchPickMediaFromGalleryIntent(GeneralOptions generalOptions) { public void chooseVideoFromGallery( @NonNull VideoSelectionOptions options, boolean usePhotoPicker, - @NonNull Result> result) { - if (!setPendingOptionsAndResult(null, options, result)) { - finishWithAlreadyActiveError(result); + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { + if (!setPendingOptionsAndResult(null, options, callback)) { + finishWithAlreadyActiveError(callback); return; } @@ -352,9 +370,13 @@ private void launchPickVideoFromGalleryIntent(Boolean usePhotoPicker) { } public void takeVideoWithCamera( - @NonNull VideoSelectionOptions options, @NonNull Result> result) { - if (!setPendingOptionsAndResult(null, options, result)) { - finishWithAlreadyActiveError(result); + @NonNull VideoSelectionOptions options, + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { + if (!setPendingOptionsAndResult(null, options, callback)) { + finishWithAlreadyActiveError(callback); return; } @@ -410,9 +432,12 @@ private void launchTakeVideoWithCameraIntent() { public void chooseImageFromGallery( @NonNull ImageSelectionOptions options, boolean usePhotoPicker, - @NonNull Result> result) { - if (!setPendingOptionsAndResult(options, null, result)) { - finishWithAlreadyActiveError(result); + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { + if (!setPendingOptionsAndResult(options, null, callback)) { + finishWithAlreadyActiveError(callback); return; } @@ -423,9 +448,12 @@ public void chooseMultiImageFromGallery( @NonNull ImageSelectionOptions options, boolean usePhotoPicker, int limit, - @NonNull Result> result) { - if (!setPendingOptionsAndResult(options, null, result)) { - finishWithAlreadyActiveError(result); + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { + if (!setPendingOptionsAndResult(options, null, callback)) { + finishWithAlreadyActiveError(callback); return; } @@ -472,9 +500,12 @@ public void chooseMultiVideoFromGallery( @NonNull VideoSelectionOptions options, boolean usePhotoPicker, int limit, - @NonNull Result> result) { - if (!setPendingOptionsAndResult(null, options, result)) { - finishWithAlreadyActiveError(result); + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { + if (!setPendingOptionsAndResult(null, options, callback)) { + finishWithAlreadyActiveError(callback); return; } @@ -501,9 +532,13 @@ private void launchMultiPickVideoFromGalleryIntent(Boolean usePhotoPicker, int l } public void takeImageWithCamera( - @NonNull ImageSelectionOptions options, @NonNull Result> result) { - if (!setPendingOptionsAndResult(options, null, result)) { - finishWithAlreadyActiveError(result); + @NonNull ImageSelectionOptions options, + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { + if (!setPendingOptionsAndResult(options, null, callback)) { + finishWithAlreadyActiveError(callback); return; } @@ -869,7 +904,7 @@ private String getResizedImagePath(String path, @NonNull ImageSelectionOptions o path, outputOptions.getMaxWidth(), outputOptions.getMaxHeight(), - outputOptions.getQuality().intValue()); + (int) outputOptions.getQuality()); } private void handleMediaResult(@NonNull ArrayList paths) { @@ -902,12 +937,15 @@ private void handleMediaResult(@NonNull ArrayList paths) { private boolean setPendingOptionsAndResult( @Nullable ImageSelectionOptions imageOptions, @Nullable VideoSelectionOptions videoOptions, - @NonNull Result> result) { + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { synchronized (pendingCallStateLock) { if (pendingCallState != null) { return false; } - pendingCallState = new PendingCallState(imageOptions, videoOptions, result); + pendingCallState = new PendingCallState(imageOptions, videoOptions, callback); } // Clean up cache if a new image picker is launched. @@ -926,57 +964,65 @@ private void finishWithSuccess(@Nullable String imagePath) { pathList.add(imagePath); } - Result> localResult = null; + Function1>, @NotNull Unit> + callback = null; synchronized (pendingCallStateLock) { if (pendingCallState != null) { - localResult = pendingCallState.result; + callback = pendingCallState.callback; } pendingCallState = null; } - if (localResult == null) { + if (callback == null) { // Only save data for later retrieval if something was actually selected. if (!pathList.isEmpty()) { cache.saveResult(pathList, null, null); } } else { - localResult.success(pathList); + ResultUtilsKt.completeWithValue(callback, pathList); } } private void finishWithListSuccess(ArrayList imagePaths) { - Result> localResult = null; + Function1>, @NotNull Unit> + callback = null; synchronized (pendingCallStateLock) { if (pendingCallState != null) { - localResult = pendingCallState.result; + callback = pendingCallState.callback; } pendingCallState = null; } - if (localResult == null) { + if (callback == null) { cache.saveResult(imagePaths, null, null); } else { - localResult.success(imagePaths); + ResultUtilsKt.completeWithValue(callback, imagePaths); } } - private void finishWithAlreadyActiveError(Result> result) { - result.error(new FlutterError("already_active", "Image picker is already active", null)); + private void finishWithAlreadyActiveError( + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { + ResultUtilsKt.completeWithError( + callback, new FlutterError("already_active", "Image picker is already active", null)); } private void finishWithError(String errorCode, String errorMessage) { - Result> localResult = null; + Function1>, @NotNull Unit> + callback = null; synchronized (pendingCallStateLock) { if (pendingCallState != null) { - localResult = pendingCallState.result; + callback = pendingCallState.callback; } pendingCallState = null; } - if (localResult == null) { + if (callback == null) { cache.saveResult(null, errorCode, errorMessage); } else { - localResult.error(new FlutterError(errorCode, errorMessage, null)); + ResultUtilsKt.completeWithError(callback, new FlutterError(errorCode, errorMessage, null)); } } diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java index 87942e2fe38..d56ae76fbc9 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java @@ -19,6 +19,10 @@ import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; import io.flutter.plugin.common.BinaryMessenger; import java.util.List; +import kotlin.Result; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; +import org.jetbrains.annotations.NotNull; @SuppressWarnings("deprecation") public class ImagePickerPlugin implements FlutterPlugin, ActivityAware, ImagePickerApi { @@ -271,10 +275,14 @@ public void pickImages( @NonNull SourceSpecification source, @NonNull ImageSelectionOptions options, @NonNull GeneralOptions generalOptions, - @NonNull Result> result) { + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { ImagePickerDelegate delegate = getImagePickerDelegate(); if (delegate == null) { - result.error( + ResultUtilsKt.completeWithError( + callback, new FlutterError( "no_activity", "image_picker plugin requires a foreground activity.", null)); return; @@ -285,14 +293,14 @@ public void pickImages( int limit = ImagePickerUtils.getLimitFromOption(generalOptions); delegate.chooseMultiImageFromGallery( - options, generalOptions.getUsePhotoPicker(), limit, result); + options, generalOptions.getUsePhotoPicker(), limit, callback); } else { switch (source.getType()) { case GALLERY: - delegate.chooseImageFromGallery(options, generalOptions.getUsePhotoPicker(), result); + delegate.chooseImageFromGallery(options, generalOptions.getUsePhotoPicker(), callback); break; case CAMERA: - delegate.takeImageWithCamera(options, result); + delegate.takeImageWithCamera(options, callback); break; } } @@ -302,15 +310,19 @@ public void pickImages( public void pickMedia( @NonNull MediaSelectionOptions mediaSelectionOptions, @NonNull GeneralOptions generalOptions, - @NonNull Result> result) { + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { ImagePickerDelegate delegate = getImagePickerDelegate(); if (delegate == null) { - result.error( + ResultUtilsKt.completeWithError( + callback, new FlutterError( "no_activity", "image_picker plugin requires a foreground activity.", null)); return; } - delegate.chooseMediaFromGallery(mediaSelectionOptions, generalOptions, result); + delegate.chooseMediaFromGallery(mediaSelectionOptions, generalOptions, callback); } @Override @@ -318,10 +330,14 @@ public void pickVideos( @NonNull SourceSpecification source, @NonNull VideoSelectionOptions options, @NonNull GeneralOptions generalOptions, - @NonNull Result> result) { + @NonNull + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback) { ImagePickerDelegate delegate = getImagePickerDelegate(); if (delegate == null) { - result.error( + ResultUtilsKt.completeWithError( + callback, new FlutterError( "no_activity", "image_picker plugin requires a foreground activity.", null)); return; @@ -331,14 +347,14 @@ public void pickVideos( if (generalOptions.getAllowMultiple()) { int limit = ImagePickerUtils.getLimitFromOption(generalOptions); delegate.chooseMultiVideoFromGallery( - options, generalOptions.getUsePhotoPicker(), limit, result); + options, generalOptions.getUsePhotoPicker(), limit, callback); } else { switch (source.getType()) { case GALLERY: - delegate.chooseVideoFromGallery(options, generalOptions.getUsePhotoPicker(), result); + delegate.chooseVideoFromGallery(options, generalOptions.getUsePhotoPicker(), callback); break; case CAMERA: - delegate.takeVideoWithCamera(options, result); + delegate.takeVideoWithCamera(options, callback); break; } } From fd77d3397eed27b8aded3b66b9020e31f49a1347 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 14 Apr 2026 10:51:17 -0400 Subject: [PATCH 6/9] Update tests --- .../imagepicker/ImagePickerCacheTest.java | 6 +- .../imagepicker/ImagePickerDelegateTest.java | 474 ++++++++++++------ .../imagepicker/ImagePickerPluginTest.java | 140 +++--- 3 files changed, 381 insertions(+), 239 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java index 20075f72348..b9c5d1c92cb 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java @@ -105,14 +105,12 @@ public void tearDown() throws Exception { public void imageCache_shouldBeAbleToSetAndGetQuality() { final int quality = 90; ImagePickerCache cache = new ImagePickerCache(mockActivity); - cache.saveDimensionWithOutputOptions( - new Messages.ImageSelectionOptions.Builder().setQuality((long) quality).build()); + cache.saveDimensionWithOutputOptions(new ImageSelectionOptions(null, null, quality)); Map resultMap = cache.getCacheMap(); int imageQuality = (int) resultMap.get(ImagePickerCache.MAP_KEY_IMAGE_QUALITY); assertThat(imageQuality, equalTo(quality)); - cache.saveDimensionWithOutputOptions( - new Messages.ImageSelectionOptions.Builder().setQuality((long) 100).build()); + cache.saveDimensionWithOutputOptions(new ImageSelectionOptions(null, null, 100)); Map resultMapWithDefaultQuality = cache.getCacheMap(); int defaultImageQuality = (int) resultMapWithDefaultQuality.get(ImagePickerCache.MAP_KEY_IMAGE_QUALITY); diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index 21f1c465de3..cedb83f34e0 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -17,7 +17,6 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.Manifest; @@ -29,16 +28,15 @@ import android.content.pm.PackageManager; import android.net.Uri; import androidx.annotation.Nullable; -import io.flutter.plugins.imagepicker.Messages.FlutterError; -import io.flutter.plugins.imagepicker.Messages.GeneralOptions; -import io.flutter.plugins.imagepicker.Messages.ImageSelectionOptions; -import io.flutter.plugins.imagepicker.Messages.MediaSelectionOptions; -import io.flutter.plugins.imagepicker.Messages.VideoSelectionOptions; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; +import kotlin.Result; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; +import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -59,17 +57,16 @@ public class ImagePickerDelegateTest { private static final Long MAX_DURATION = 10L; private static final Integer IMAGE_QUALITY = 90; private static final ImageSelectionOptions DEFAULT_IMAGE_OPTIONS = - new ImageSelectionOptions.Builder().setQuality((long) 100).build(); + new ImageSelectionOptions(null, null, 100); private static final ImageSelectionOptions RESIZE_TRIGGERING_IMAGE_OPTIONS = - new ImageSelectionOptions.Builder().setQuality((long) 100).setMaxWidth(WIDTH).build(); + new ImageSelectionOptions(WIDTH, null, 100); private static final VideoSelectionOptions DEFAULT_VIDEO_OPTIONS = - new VideoSelectionOptions.Builder().build(); + new VideoSelectionOptions(null); private static final MediaSelectionOptions DEFAULT_MEDIA_OPTIONS = - new MediaSelectionOptions.Builder().setImageSelectionOptions(DEFAULT_IMAGE_OPTIONS).build(); + new MediaSelectionOptions(DEFAULT_IMAGE_OPTIONS); @Mock Activity mockActivity; @Mock ImageResizer mockImageResizer; - @Mock Messages.Result> mockResult; @Mock ImagePickerDelegate.PermissionManager mockPermissionManager; @Mock FileUtils mockFileUtils; @Mock Intent mockIntent; @@ -147,43 +144,77 @@ public void whenConstructed_setsCorrectFileProviderName() { @Test public void chooseImageFromGallery_whenPendingResultExists_finishesWithAlreadyActiveError() { ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); - - delegate.chooseImageFromGallery(DEFAULT_IMAGE_OPTIONS, false, mockResult); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback(reply -> null), DEFAULT_IMAGE_OPTIONS, null); + + final Boolean[] callbackCalled = new Boolean[1]; + delegate.chooseImageFromGallery( + DEFAULT_IMAGE_OPTIONS, + false, + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertIsAlreadyActiveError(error); + return null; + })); - verifyFinishedWithAlreadyActiveError(); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test public void chooseMultiImageFromGallery_whenPendingResultExists_finishesWithAlreadyActiveError() { ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback(reply -> null), DEFAULT_IMAGE_OPTIONS, null); + final Boolean[] callbackCalled = new Boolean[1]; delegate.chooseMultiImageFromGallery( - DEFAULT_IMAGE_OPTIONS, false, Integer.MAX_VALUE, mockResult); + DEFAULT_IMAGE_OPTIONS, + false, + Integer.MAX_VALUE, + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertIsAlreadyActiveError(error); + return null; + })); - verifyFinishedWithAlreadyActiveError(); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test public void chooseMediaFromGallery_whenPendingResultExists_finishesWithAlreadyActiveError() { ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback(reply -> null), DEFAULT_IMAGE_OPTIONS, null); GeneralOptions generalOptions = - new GeneralOptions.Builder().setAllowMultiple(true).setUsePhotoPicker(true).build(); - delegate.chooseMediaFromGallery(DEFAULT_MEDIA_OPTIONS, generalOptions, mockResult); + new GeneralOptions(/* allowMultiple */ true, /* usePhotoPicker */ true, null); + final Boolean[] callbackCalled = new Boolean[1]; + delegate.chooseMediaFromGallery( + DEFAULT_MEDIA_OPTIONS, + generalOptions, + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertIsAlreadyActiveError(error); + return null; + })); - verifyFinishedWithAlreadyActiveError(); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @Config(sdk = 30) public void chooseImageFromGallery_launchesChooseFromGalleryIntent() { ImagePickerDelegate delegate = createDelegate(); - delegate.chooseImageFromGallery(DEFAULT_IMAGE_OPTIONS, false, mockResult); + delegate.chooseImageFromGallery( + DEFAULT_IMAGE_OPTIONS, false, ResultCompat.asCompatCallback(reply -> null)); verify(mockActivity) .startActivityForResult( @@ -194,7 +225,8 @@ public void chooseImageFromGallery_launchesChooseFromGalleryIntent() { @Config(minSdk = 33) public void chooseImageFromGallery_withPhotoPicker_launchesChooseFromGalleryIntent() { ImagePickerDelegate delegate = createDelegate(); - delegate.chooseImageFromGallery(DEFAULT_IMAGE_OPTIONS, true, mockResult); + delegate.chooseImageFromGallery( + DEFAULT_IMAGE_OPTIONS, true, ResultCompat.asCompatCallback(reply -> null)); verify(mockActivity) .startActivityForResult( @@ -206,7 +238,10 @@ public void chooseImageFromGallery_withPhotoPicker_launchesChooseFromGalleryInte public void chooseMultiImageFromGallery_launchesChooseFromGalleryIntent() { ImagePickerDelegate delegate = createDelegate(); delegate.chooseMultiImageFromGallery( - DEFAULT_IMAGE_OPTIONS, true, Integer.MAX_VALUE, mockResult); + DEFAULT_IMAGE_OPTIONS, + true, + Integer.MAX_VALUE, + ResultCompat.asCompatCallback(reply -> null)); verify(mockActivity) .startActivityForResult( @@ -219,7 +254,10 @@ public void chooseMultiImageFromGallery_launchesChooseFromGalleryIntent() { public void chooseMultiImageFromGallery_withPhotoPicker_launchesChooseFromGalleryIntent() { ImagePickerDelegate delegate = createDelegate(); delegate.chooseMultiImageFromGallery( - DEFAULT_IMAGE_OPTIONS, false, Integer.MAX_VALUE, mockResult); + DEFAULT_IMAGE_OPTIONS, + false, + Integer.MAX_VALUE, + ResultCompat.asCompatCallback(reply -> null)); verify(mockActivity) .startActivityForResult( @@ -231,7 +269,8 @@ public void chooseMultiImageFromGallery_withPhotoPicker_launchesChooseFromGaller @Config(sdk = 30) public void chooseVideoFromGallery_launchesChooseFromGalleryIntent() { ImagePickerDelegate delegate = createDelegate(); - delegate.chooseVideoFromGallery(DEFAULT_VIDEO_OPTIONS, true, mockResult); + delegate.chooseVideoFromGallery( + DEFAULT_VIDEO_OPTIONS, true, ResultCompat.asCompatCallback(reply -> null)); verify(mockActivity) .startActivityForResult( @@ -242,7 +281,8 @@ public void chooseVideoFromGallery_launchesChooseFromGalleryIntent() { @Config(minSdk = 33) public void chooseVideoFromGallery_withPhotoPicker_launchesChooseFromGalleryIntent() { ImagePickerDelegate delegate = createDelegate(); - delegate.chooseVideoFromGallery(DEFAULT_VIDEO_OPTIONS, true, mockResult); + delegate.chooseVideoFromGallery( + DEFAULT_VIDEO_OPTIONS, true, ResultCompat.asCompatCallback(reply -> null)); verify(mockActivity) .startActivityForResult( @@ -252,12 +292,22 @@ public void chooseVideoFromGallery_withPhotoPicker_launchesChooseFromGalleryInte @Test public void takeImageWithCamera_whenPendingResultExists_finishesWithAlreadyActiveError() { ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); - - delegate.takeImageWithCamera(DEFAULT_IMAGE_OPTIONS, mockResult); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback(reply -> null), DEFAULT_IMAGE_OPTIONS, null); + + final Boolean[] callbackCalled = new Boolean[1]; + delegate.takeImageWithCamera( + DEFAULT_IMAGE_OPTIONS, + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertIsAlreadyActiveError(error); + return null; + })); - verifyFinishedWithAlreadyActiveError(); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -266,7 +316,8 @@ public void takeImageWithCamera_whenHasNoCameraPermission_requestsForPermission( when(mockPermissionManager.needRequestCameraPermission()).thenReturn(true); ImagePickerDelegate delegate = createDelegate(); - delegate.takeImageWithCamera(DEFAULT_IMAGE_OPTIONS, mockResult); + delegate.takeImageWithCamera( + DEFAULT_IMAGE_OPTIONS, ResultCompat.asCompatCallback(reply -> null)); verify(mockPermissionManager) .askForPermission( @@ -278,7 +329,8 @@ public void takeImageWithCamera_whenCameraPermissionNotPresent_requestsForPermis when(mockPermissionManager.needRequestCameraPermission()).thenReturn(false); ImagePickerDelegate delegate = createDelegate(); - delegate.takeImageWithCamera(DEFAULT_IMAGE_OPTIONS, mockResult); + delegate.takeImageWithCamera( + DEFAULT_IMAGE_OPTIONS, ResultCompat.asCompatCallback(reply -> null)); verify(mockActivity) .startActivityForResult( @@ -291,7 +343,8 @@ public void takeImageWithCamera_whenCameraPermissionNotPresent_requestsForPermis when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true); ImagePickerDelegate delegate = createDelegate(); - delegate.takeImageWithCamera(DEFAULT_IMAGE_OPTIONS, mockResult); + delegate.takeImageWithCamera( + DEFAULT_IMAGE_OPTIONS, ResultCompat.asCompatCallback(reply -> null)); verify(mockActivity) .startActivityForResult( @@ -306,13 +359,20 @@ public void takeImageWithCamera_whenCameraPermissionNotPresent_requestsForPermis .when(mockActivity) .startActivityForResult(any(Intent.class), anyInt()); ImagePickerDelegate delegate = createDelegate(); - delegate.takeImageWithCamera(DEFAULT_IMAGE_OPTIONS, mockResult); + final Boolean[] callbackCalled = new Boolean[1]; + delegate.takeImageWithCamera( + DEFAULT_IMAGE_OPTIONS, + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertEquals("no_available_camera", error.getCode()); + assertEquals("No cameras available for taking pictures.", error.getMessage()); + return null; + })); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(mockResult).error(errorCaptor.capture()); - assertEquals("no_available_camera", errorCaptor.getValue().code); - assertEquals("No cameras available for taking pictures.", errorCaptor.getValue().getMessage()); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -320,7 +380,8 @@ public void takeImageWithCamera_writesImageToCacheDirectory() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true); ImagePickerDelegate delegate = createDelegate(); - delegate.takeImageWithCamera(DEFAULT_IMAGE_OPTIONS, mockResult); + delegate.takeImageWithCamera( + DEFAULT_IMAGE_OPTIONS, ResultCompat.asCompatCallback(reply -> null)); mockStaticFile.verify( () -> File.createTempFile(any(), eq(".jpg"), eq(externalDirectory)), times(1)); @@ -328,26 +389,34 @@ public void takeImageWithCamera_writesImageToCacheDirectory() { @Test public void onRequestPermissionsResult_whenCameraPermissionDenied_finishesWithError() { + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertEquals("camera_access_denied", error.getCode()); + assertEquals("The user did not allow camera access.", error.getMessage()); + return null; + }), + DEFAULT_IMAGE_OPTIONS, + null); delegate.onRequestPermissionsResult( ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION, new String[] {Manifest.permission.CAMERA}, new int[] {PackageManager.PERMISSION_DENIED}); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(mockResult).error(errorCaptor.capture()); - assertEquals("camera_access_denied", errorCaptor.getValue().code); - assertEquals("The user did not allow camera access.", errorCaptor.getValue().getMessage()); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test public void onRequestTakeVideoPermissionsResult_whenCameraPermissionGranted_launchesTakeVideoWithCameraIntent() { ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(null, DEFAULT_VIDEO_OPTIONS); + createDelegateWithPendingCallbackAndOptions(null, null, DEFAULT_VIDEO_OPTIONS); delegate.onRequestPermissionsResult( ImagePickerDelegate.REQUEST_CAMERA_VIDEO_PERMISSION, new String[] {Manifest.permission.CAMERA}, @@ -362,7 +431,7 @@ public void onRequestPermissionsResult_whenCameraPermissionDenied_finishesWithEr public void onRequestTakeImagePermissionsResult_whenCameraPermissionGranted_launchesTakeWithCameraIntent() { ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions(null, DEFAULT_IMAGE_OPTIONS, null); delegate.onRequestPermissionsResult( ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION, new String[] {Manifest.permission.CAMERA}, @@ -382,17 +451,23 @@ public void onActivityResult_whenPickFromGalleryCanceled_finishesWithEmptyList() }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertTrue(reply.getOrNull().isEmpty()); + return null; + }), + DEFAULT_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals(0, pathListCapture.getValue().size()); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -422,17 +497,23 @@ public void onActivityResult_whenPickFromGalleryCanceled_storesNothingInCache() }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertEquals("originalPath", reply.getOrNull().get(0)); + return null; + }), + DEFAULT_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals("originalPath", pathListCapture.getValue().get(0)); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -449,17 +530,23 @@ public void onActivityResult_whenPickFromGalleryCanceled_storesNothingInCache() }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertEquals("originalPath", reply.getOrNull().get(0)); + return null; + }), + DEFAULT_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals("originalPath", pathListCapture.getValue().get(0)); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -476,17 +563,23 @@ public void onActivityResult_whenPickFromGalleryCanceled_storesNothingInCache() }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(null, DEFAULT_VIDEO_OPTIONS); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertEquals("pathFromUri", reply.getOrNull().get(0)); + return null; + }), + null, + DEFAULT_VIDEO_OPTIONS); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals("pathFromUri", pathListCapture.getValue().get(0)); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -503,17 +596,25 @@ public void onActivityResult_whenPickFromGalleryCanceled_storesNothingInCache() }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertEquals("no_valid_image_uri", error.getCode()); + assertEquals("Cannot find the selected image.", error.getMessage()); + return null; + }), + DEFAULT_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(mockResult).error(errorCaptor.capture()); - assertEquals("no_valid_image_uri", errorCaptor.getValue().code); - assertEquals("Cannot find the selected image.", errorCaptor.getValue().getMessage()); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -529,18 +630,25 @@ public void onActivityResult_whenVideoPickedFromGallery_nullUri_finishesWithNoVa }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(null, DEFAULT_VIDEO_OPTIONS); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertEquals("no_valid_video_uri", error.getCode()); + assertEquals("Cannot find the selected video.", error.getMessage()); + return null; + }), + null, + DEFAULT_VIDEO_OPTIONS); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(mockResult).error(errorCaptor.capture()); - assertEquals("no_valid_video_uri", errorCaptor.getValue().code); - assertEquals("Cannot find the selected video.", errorCaptor.getValue().getMessage()); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -573,17 +681,23 @@ public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_stores }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertEquals("scaledPath", reply.getOrNull().get(0)); + return null; + }), + RESIZE_TRIGGERING_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals("scaledPath", pathListCapture.getValue().get(0)); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -596,17 +710,23 @@ public void onActivityResult_whenImagePickedFromGallery_andNoResizeNeeded_stores }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertEquals("pathFromUri", reply.getOrNull().get(0)); + return null; + }), + RESIZE_TRIGGERING_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals("pathFromUri", pathListCapture.getValue().get(0)); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -618,17 +738,23 @@ public void onActivityResult_whenTakeImageWithCameraCanceled_finishesWithEmptyLi }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertTrue(reply.getOrNull().isEmpty()); + return null; + }), + DEFAULT_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_CANCELED, null); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals(0, pathListCapture.getValue().size()); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -641,17 +767,23 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertEquals("originalPath", reply.getOrNull().get(0)); + return null; + }), + DEFAULT_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals("originalPath", pathListCapture.getValue().get(0)); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -665,17 +797,23 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertEquals("scaledPath", reply.getOrNull().get(0)); + return null; + }), + RESIZE_TRIGGERING_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals("scaledPath", pathListCapture.getValue().get(0)); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -689,17 +827,23 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(RESIZE_TRIGGERING_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertEquals("pathFromUri", reply.getOrNull().get(0)); + return null; + }), + RESIZE_TRIGGERING_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals("pathFromUri", pathListCapture.getValue().get(0)); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -713,18 +857,23 @@ public void onActivityResult_whenImageTakenWithCamera_andNoResizeNeeded_finishes }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions( - null, new VideoSelectionOptions.Builder().setMaxDurationSeconds(MAX_DURATION).build()); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isSuccess()); + assertEquals("pathFromUri", reply.getOrNull().get(0)); + return null; + }), + null, + new VideoSelectionOptions(MAX_DURATION)); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); - @SuppressWarnings("unchecked") - ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(List.class); - verify(mockResult).success(pathListCapture.capture()); - assertEquals("pathFromUri", pathListCapture.getValue().get(0)); - verifyNoMoreInteractions(mockResult); + assertTrue(callbackCalled[0]); } @Test @@ -826,16 +975,25 @@ public void onActivityResult_whenImagePickedFromGallery_finishesWithErrorIfClipD }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertEquals("no_valid_media_uri", error.getCode()); + assertEquals("Cannot find the selected media.", error.getMessage()); + return null; + }), + DEFAULT_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY, Activity.RESULT_OK, mockIntent); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(mockResult).error(errorCaptor.capture()); - assertEquals("no_valid_media_uri", errorCaptor.getValue().code); - assertEquals("Cannot find the selected media.", errorCaptor.getValue().getMessage()); + assertTrue(callbackCalled[0]); } @Test @@ -851,16 +1009,25 @@ public void onActivityResult_whenImagePickedFromGallery_finishesWithErrorIfClipD }) .when(mockExecutor) .execute(any(Runnable.class)); + final Boolean[] callbackCalled = new Boolean[1]; ImagePickerDelegate delegate = - createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); + createDelegateWithPendingCallbackAndOptions( + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertEquals("no_valid_media_uri", error.getCode()); + assertEquals("Cannot find the selected media.", error.getMessage()); + return null; + }), + DEFAULT_IMAGE_OPTIONS, + null); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY, Activity.RESULT_OK, mockIntent); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(mockResult).error(errorCaptor.capture()); - assertEquals("no_valid_media_uri", errorCaptor.getValue().code); - assertEquals("Cannot find the selected media.", errorCaptor.getValue().getMessage()); + assertTrue(callbackCalled[0]); } private ImagePickerDelegate createDelegate() { @@ -877,14 +1044,19 @@ private ImagePickerDelegate createDelegate() { mockExecutor); } - private ImagePickerDelegate createDelegateWithPendingResultAndOptions( - @Nullable ImageSelectionOptions imageOptions, @Nullable VideoSelectionOptions videoOptions) { + private ImagePickerDelegate createDelegateWithPendingCallbackAndOptions( + @Nullable + Function1< + ? super @NotNull Result>, @NotNull Unit> + callback, + @Nullable ImageSelectionOptions imageOptions, + @Nullable VideoSelectionOptions videoOptions) { return new ImagePickerDelegate( mockActivity, mockImageResizer, imageOptions, videoOptions, - mockResult, + callback, cache, mockPermissionManager, mockFileUriResolver, @@ -892,11 +1064,9 @@ private ImagePickerDelegate createDelegateWithPendingResultAndOptions( mockExecutor); } - private void verifyFinishedWithAlreadyActiveError() { - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(mockResult).error(errorCaptor.capture()); - assertEquals("already_active", errorCaptor.getValue().code); - assertEquals("Image picker is already active", errorCaptor.getValue().getMessage()); + private void assertIsAlreadyActiveError(FlutterError error) { + assertEquals("already_active", error.getCode()); + assertEquals("Image picker is already active", error.getMessage()); } private void setupMockClipData() { diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java index 3988516e77d..cea8c5fdab3 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java @@ -23,60 +23,37 @@ import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugins.imagepicker.Messages.FlutterError; -import io.flutter.plugins.imagepicker.Messages.GeneralOptions; -import io.flutter.plugins.imagepicker.Messages.ImageSelectionOptions; -import io.flutter.plugins.imagepicker.Messages.MediaSelectionOptions; -import io.flutter.plugins.imagepicker.Messages.SourceSpecification; -import io.flutter.plugins.imagepicker.Messages.VideoSelectionOptions; -import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class ImagePickerPluginTest { private static final ImageSelectionOptions DEFAULT_IMAGE_OPTIONS = - new ImageSelectionOptions.Builder().setQuality((long) 100).build(); - private static final VideoSelectionOptions DEFAULT_VIDEO_OPTIONS = - new VideoSelectionOptions.Builder().build(); + new ImageSelectionOptions(/* maxWidth */ null, /* maxHeight */ null, /* quality */ 100); + private static final VideoSelectionOptions DEFAULT_VIDEO_OPTIONS = new VideoSelectionOptions(); private static final MediaSelectionOptions DEFAULT_MEDIA_OPTIONS = - new MediaSelectionOptions.Builder().setImageSelectionOptions(DEFAULT_IMAGE_OPTIONS).build(); + new MediaSelectionOptions(DEFAULT_IMAGE_OPTIONS); private static final GeneralOptions GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER = - new GeneralOptions.Builder().setUsePhotoPicker(true).setAllowMultiple(true).build(); + new GeneralOptions(/* allowMultiple */ true, /* usePhotoPicker */ true, /* limit */ null); private static final GeneralOptions GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_USE_PHOTO_PICKER = - new GeneralOptions.Builder().setUsePhotoPicker(true).setAllowMultiple(false).build(); + new GeneralOptions(/* allowMultiple */ false, /* usePhotoPicker */ true, /* limit */ null); private static final GeneralOptions GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER = - new GeneralOptions.Builder().setUsePhotoPicker(false).setAllowMultiple(false).build(); + new GeneralOptions(/* allowMultiple */ false, /* usePhotoPicker */ false, /* limit */ null); private static final GeneralOptions GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER = - new GeneralOptions.Builder().setUsePhotoPicker(false).setAllowMultiple(true).build(); + new GeneralOptions(/* allowMultiple */ true, /* usePhotoPicker */ false, /* limit */ null); private static final GeneralOptions GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER_WITH_LIMIT = - new GeneralOptions.Builder() - .setUsePhotoPicker(false) - .setAllowMultiple(true) - .setLimit((long) 5) - .build(); + new GeneralOptions(/* allowMultiple */ true, /* usePhotoPicker */ false, /* limit */ 5L); private static final GeneralOptions GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER_WITH_LIMIT = - new GeneralOptions.Builder() - .setUsePhotoPicker(true) - .setAllowMultiple(true) - .setLimit((long) 5) - .build(); + new GeneralOptions(/* allowMultiple */ true, /* usePhotoPicker */ true, /* limit */ 5L); private static final SourceSpecification SOURCE_GALLERY = - new SourceSpecification.Builder().setType(Messages.SourceType.GALLERY).build(); + new SourceSpecification(SourceType.GALLERY, null); private static final SourceSpecification SOURCE_CAMERA_FRONT = - new SourceSpecification.Builder() - .setType(Messages.SourceType.CAMERA) - .setCamera(Messages.SourceCamera.FRONT) - .build(); + new SourceSpecification(SourceType.CAMERA, SourceCamera.FRONT); private static final SourceSpecification SOURCE_CAMERA_REAR = - new SourceSpecification.Builder() - .setType(Messages.SourceType.CAMERA) - .setCamera(Messages.SourceCamera.REAR) - .build(); + new SourceSpecification(SourceType.CAMERA, SourceCamera.REAR); @Mock ActivityPluginBinding mockActivityBinding; @Mock FlutterPluginBinding mockPluginBinding; @@ -84,7 +61,6 @@ public class ImagePickerPluginTest { @Mock Activity mockActivity; @Mock Application mockApplication; @Mock ImagePickerDelegate mockImagePickerDelegate; - @Mock Messages.Result> mockResult; ImagePickerPlugin plugin; @@ -107,17 +83,22 @@ public void tearDown() throws Exception { public void pickImages_whenActivityIsNull_finishesWithForegroundActivityRequiredError() { ImagePickerPlugin imagePickerPluginWithNullActivity = new ImagePickerPlugin(mockImagePickerDelegate, null); + final Boolean[] callbackCalled = new Boolean[1]; imagePickerPluginWithNullActivity.pickImages( SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER, - mockResult); - - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(mockResult).error(errorCaptor.capture()); - assertEquals("no_activity", errorCaptor.getValue().code); - assertEquals( - "image_picker plugin requires a foreground activity.", errorCaptor.getValue().getMessage()); + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertEquals("no_activity", error.getCode()); + assertEquals( + "image_picker plugin requires a foreground activity.", error.getMessage()); + return null; + })); + assertTrue(callbackCalled[0]); verifyNoInteractions(mockImagePickerDelegate); } @@ -125,17 +106,22 @@ public void pickImages_whenActivityIsNull_finishesWithForegroundActivityRequired public void pickVideos_whenActivityIsNull_finishesWithForegroundActivityRequiredError() { ImagePickerPlugin imagePickerPluginWithNullActivity = new ImagePickerPlugin(mockImagePickerDelegate, null); + final Boolean[] callbackCalled = new Boolean[1]; imagePickerPluginWithNullActivity.pickVideos( SOURCE_CAMERA_REAR, DEFAULT_VIDEO_OPTIONS, GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, - mockResult); - - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(mockResult).error(errorCaptor.capture()); - assertEquals("no_activity", errorCaptor.getValue().code); - assertEquals( - "image_picker plugin requires a foreground activity.", errorCaptor.getValue().getMessage()); + ResultCompat.asCompatCallback( + reply -> { + callbackCalled[0] = true; + assertTrue(reply.isFailure()); + FlutterError error = (FlutterError) reply.exceptionOrNull(); + assertEquals("no_activity", error.getCode()); + assertEquals( + "image_picker plugin requires a foreground activity.", error.getMessage()); + return null; + })); + assertTrue(callbackCalled[0]); verifyNoInteractions(mockImagePickerDelegate); } @@ -146,7 +132,7 @@ public void retrieveLostResults_whenActivityIsNull_finishesWithForegroundActivit FlutterError error = assertThrows(FlutterError.class, imagePickerPluginWithNullActivity::retrieveLostResults); assertEquals("image_picker plugin requires a foreground activity.", error.getMessage()); - assertEquals("no_activity", error.code); + assertEquals("no_activity", error.getCode()); verifyNoInteractions(mockImagePickerDelegate); } @@ -156,9 +142,8 @@ public void pickImages_whenSourceIsGallery_invokesChooseImageFromGallery() { SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate).chooseImageFromGallery(any(), eq(false), any()); - verifyNoInteractions(mockResult); } @Test @@ -167,9 +152,8 @@ public void pickImages_whenSourceIsGalleryUsingPhotoPicker_invokesChooseImageFro SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate).chooseImageFromGallery(any(), eq(true), any()); - verifyNoInteractions(mockResult); } @Test @@ -178,10 +162,9 @@ public void pickImages_invokesChooseMultiImageFromGallery() { SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate) .chooseMultiImageFromGallery(any(), eq(false), eq(Integer.MAX_VALUE), any()); - verifyNoInteractions(mockResult); } @Test @@ -190,10 +173,9 @@ public void pickImages_usingPhotoPicker_invokesChooseMultiImageFromGallery() { SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate) .chooseMultiImageFromGallery(any(), eq(true), eq(Integer.MAX_VALUE), any()); - verifyNoInteractions(mockResult); } @Test @@ -202,9 +184,8 @@ public void pickImages_usingPhotoPicker_withLimit5_invokesChooseMultiImageFromGa SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER_WITH_LIMIT, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate).chooseMultiImageFromGallery(any(), eq(true), eq(5), any()); - verifyNoInteractions(mockResult); } @Test @@ -213,39 +194,36 @@ public void pickImages_withLimit5_invokesChooseMultiImageFromGallery() { SOURCE_GALLERY, DEFAULT_IMAGE_OPTIONS, GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER_WITH_LIMIT, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate).chooseMultiImageFromGallery(any(), eq(false), eq(5), any()); - verifyNoInteractions(mockResult); } @Test public void pickMedia_invokesChooseMediaFromGallery() { - MediaSelectionOptions mediaSelectionOptions = - new MediaSelectionOptions.Builder().setImageSelectionOptions(DEFAULT_IMAGE_OPTIONS).build(); + MediaSelectionOptions mediaSelectionOptions = new MediaSelectionOptions(DEFAULT_IMAGE_OPTIONS); plugin.pickMedia( mediaSelectionOptions, GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate) .chooseMediaFromGallery( eq(mediaSelectionOptions), eq(GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER), any()); - verifyNoInteractions(mockResult); } @Test public void pickMedia_usingPhotoPicker_invokesChooseMediaFromGallery() { - MediaSelectionOptions mediaSelectionOptions = - new MediaSelectionOptions.Builder().setImageSelectionOptions(DEFAULT_IMAGE_OPTIONS).build(); + MediaSelectionOptions mediaSelectionOptions = new MediaSelectionOptions(DEFAULT_IMAGE_OPTIONS); plugin.pickMedia( - mediaSelectionOptions, GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, mockResult); + mediaSelectionOptions, + GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate) .chooseMediaFromGallery( eq(mediaSelectionOptions), eq(GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER), any()); - verifyNoInteractions(mockResult); } @Test @@ -254,9 +232,8 @@ public void pickImages_whenSourceIsCamera_invokesTakeImageWithCamera() { SOURCE_CAMERA_REAR, DEFAULT_IMAGE_OPTIONS, GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate).takeImageWithCamera(any(), any()); - verifyNoInteractions(mockResult); } @Test @@ -265,7 +242,7 @@ public void pickImages_whenSourceIsCamera_invokesTakeImageWithCamera_RearCamera( SOURCE_CAMERA_REAR, DEFAULT_IMAGE_OPTIONS, GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate).setCameraDevice(eq(ImagePickerDelegate.CameraDevice.REAR)); } @@ -275,7 +252,7 @@ public void pickImages_whenSourceIsCamera_invokesTakeImageWithCamera_FrontCamera SOURCE_CAMERA_FRONT, DEFAULT_IMAGE_OPTIONS, GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate).setCameraDevice(eq(ImagePickerDelegate.CameraDevice.FRONT)); } @@ -285,7 +262,7 @@ public void pickVideos_whenSourceIsCamera_invokesTakeImageWithCamera_RearCamera( SOURCE_CAMERA_REAR, DEFAULT_VIDEO_OPTIONS, GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate).setCameraDevice(eq(ImagePickerDelegate.CameraDevice.REAR)); } @@ -295,7 +272,7 @@ public void pickVideos_whenSourceIsCamera_invokesTakeImageWithCamera_FrontCamera SOURCE_CAMERA_FRONT, DEFAULT_VIDEO_OPTIONS, GENERAL_OPTIONS_DONT_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate).setCameraDevice(eq(ImagePickerDelegate.CameraDevice.FRONT)); } @@ -305,10 +282,9 @@ public void pickVideos_invokesChooseMultiVideoFromGallery() { SOURCE_GALLERY, DEFAULT_VIDEO_OPTIONS, GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate) .chooseMultiVideoFromGallery(any(), eq(false), eq(Integer.MAX_VALUE), any()); - verifyNoInteractions(mockResult); } @Test @@ -317,10 +293,9 @@ public void pickVideos_usingPhotoPicker_invokesChooseMultiVideoFromGallery() { SOURCE_GALLERY, DEFAULT_VIDEO_OPTIONS, GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate) .chooseMultiVideoFromGallery(any(), eq(true), eq(Integer.MAX_VALUE), any()); - verifyNoInteractions(mockResult); } @Test @@ -329,9 +304,8 @@ public void pickVideos_withLimit5_invokesChooseMultiVideoFromGallery() { SOURCE_GALLERY, DEFAULT_VIDEO_OPTIONS, GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER_WITH_LIMIT, - mockResult); + ResultCompat.asCompatCallback(reply -> null)); verify(mockImagePickerDelegate).chooseMultiVideoFromGallery(any(), eq(false), eq(5), any()); - verifyNoInteractions(mockResult); } @Test From ede2ce77c03818344e031a8d6c607c2084aa6128 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 14 Apr 2026 10:54:34 -0400 Subject: [PATCH 7/9] arg comments --- .../plugins/imagepicker/ImageResizerTest.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java index f34f3e03b8b..9d5edd1030b 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java @@ -73,25 +73,31 @@ public void tearDown() throws Exception { @Test public void onResizeImageIfNeeded_whenQualityIsMax_shouldNotResize_returnTheUnscaledFile() { - String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, 100); + String outputFile = + resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, /* imageQuality */ 100); assertThat(outputFile, equalTo(imageFile.getPath())); } @Test public void onResizeImageIfNeeded_whenQualityIsNotMax_shouldResize_returnResizedFile() { - String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, 50); + String outputFile = + resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, /* imageQuality */ 50); assertThat(outputFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png")); } @Test public void onResizeImageIfNeeded_whenWidthIsNotNull_shouldResize_returnResizedFile() { - String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, null, 100); + String outputFile = + resizer.resizeImageIfNeeded( + imageFile.getPath(), /* maxWidth */ 50.0, /* maxHeight */ null, /* imageQuality */ 100); assertThat(outputFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png")); } @Test public void onResizeImageIfNeeded_whenHeightIsNotNull_shouldResize_returnResizedFile() { - String outputFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, 100); + String outputFile = + resizer.resizeImageIfNeeded( + imageFile.getPath(), /* maxWidth */ null, /* maxHeight */ 50.0, /* imageQuality */ 100); assertThat(outputFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png")); } From 12fd60242504f31e7d77b001fb1fea54a614cd07 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 14 Apr 2026 10:58:25 -0400 Subject: [PATCH 8/9] Version bump --- packages/image_picker/image_picker_android/CHANGELOG.md | 4 ++++ packages/image_picker/image_picker_android/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 6e4a360aa63..7a46bc441ba 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.13+17 + +* Updates internal implementation to use Kotlin Pigeon. + ## 0.8.13+16 * Bumps androidx.core:core from 1.17.0 to 1.18.0. diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index b23de7a0c23..c34caf420c7 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_android description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.13+16 +version: 0.8.13+17 environment: sdk: ^3.9.0 From 47a39f704f1b2ff29dfcc9a8a6e38235efc31ade Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 14 Apr 2026 11:02:38 -0400 Subject: [PATCH 9/9] Add meta --- packages/image_picker/image_picker_android/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index c34caf420c7..bab4361ba7e 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 image_picker_platform_interface: ^2.11.0 + meta: ^1.10.0 dev_dependencies: flutter_test: