[image_picker] Don't store null paths in lost cache (#6678)

If the user cancels image selection on Android, store nothing in the
lost image cache rather than storing an array with a null path.

While we could potentially keep this behavior and instead handle it
differently on the Dart side, returning some new "cancelled"
`LostDataResponse`, that would be semi-breaking; e.g., the current
example's lost data handling would actually throw as written if we had a
new non-`isEmpty`, non-exception, null-`file` response. Since nobody has
requested the ability to specifically detect a "lost cancel" as being
different from not having started the process of picking anything, this
doesn't make that potentially-client-breaking change. If it turns out
there's a use case for that in the future, we can revisit that (but
should not do it by storing a null entry in a file array anyway).

Fixes https://github.com/flutter/flutter/issues/114551
diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md
index 3075f5b..b041761 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.5+4
+
+* Fixes null cast exception when restoring a cancelled selection.
+
 ## 0.8.5+3
 
 * Updates minimum Flutter version to 2.10.
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 dddf67e..cb4beac 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
@@ -15,6 +15,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.provider.MediaStore;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.app.ActivityCompat;
 import androidx.core.content.FileProvider;
@@ -621,11 +622,18 @@
     return true;
   }
 
-  private void finishWithSuccess(String imagePath) {
+  // Handles completion of selection with a single result.
+  //
+  // A null imagePath indicates that the image picker was cancelled without
+  // selection.
+  private void finishWithSuccess(@Nullable String imagePath) {
     if (pendingResult == null) {
-      ArrayList<String> pathList = new ArrayList<>();
-      pathList.add(imagePath);
-      cache.saveResult(pathList, null, null);
+      // Only save data for later retrieval if something was actually selected.
+      if (imagePath != null) {
+        ArrayList<String> pathList = new ArrayList<>();
+        pathList.add(imagePath);
+        cache.saveResult(pathList, null, null);
+      }
       return;
     }
     pendingResult.success(imagePath);
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 d2ee7b0..6d1e73c 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
@@ -13,6 +13,7 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -276,6 +277,16 @@
   }
 
   @Test
+  public void onActivityResult_WhenPickFromGalleryCanceled_StoresNothingInCache() {
+    ImagePickerDelegate delegate = createDelegate();
+
+    delegate.onActivityResult(
+        ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null);
+
+    verify(cache, never()).saveResult(any(), any(), any());
+  }
+
+  @Test
   public void
       onActivityResult_WhenImagePickedFromGallery_AndNoResizeNeeded_FinishesWithImagePath() {
     ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall();
@@ -288,6 +299,18 @@
   }
 
   @Test
+  public void onActivityResult_WhenImagePickedFromGallery_AndNoResizeNeeded_StoresImageInCache() {
+    ImagePickerDelegate delegate = createDelegate();
+
+    delegate.onActivityResult(
+        ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent);
+
+    ArgumentCaptor<ArrayList<String>> pathListCapture = ArgumentCaptor.forClass(ArrayList.class);
+    verify(cache, times(1)).saveResult(pathListCapture.capture(), any(), any());
+    assertEquals("pathFromUri", pathListCapture.getValue().get(0));
+  }
+
+  @Test
   public void
       onActivityResult_WhenImagePickedFromGallery_AndResizeNeeded_FinishesWithScaledImagePath() {
     when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH);
diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml
index c009249..4c32e34 100755
--- a/packages/image_picker/image_picker_android/pubspec.yaml
+++ b/packages/image_picker/image_picker_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Android implementation of the image_picker plugin.
 repository: https://github.com/flutter/plugins/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.5+3
+version: 0.8.5+4
 
 environment:
   sdk: ">=2.14.0 <3.0.0"