fixes FadeInImage for an edge case (#111035) (#113035)

Co-authored-by: Viren Khatri <44755140+werainkhatri@users.noreply.github.com>
diff --git a/packages/flutter/lib/src/widgets/fade_in_image.dart b/packages/flutter/lib/src/widgets/fade_in_image.dart
index ec42f66..d2e6913 100644
--- a/packages/flutter/lib/src/widgets/fade_in_image.dart
+++ b/packages/flutter/lib/src/widgets/fade_in_image.dart
@@ -539,7 +539,8 @@
 
   @override
   Widget build(BuildContext context) {
-    if (widget.wasSynchronouslyLoaded || _placeholderOpacityAnimation!.isCompleted) {
+    if (widget.wasSynchronouslyLoaded ||
+        (_placeholderOpacityAnimation?.isCompleted ?? true)) {
       return widget.target;
     }
 
diff --git a/packages/flutter/test/widgets/fade_in_image_test.dart b/packages/flutter/test/widgets/fade_in_image_test.dart
index d0f3cd5..e3b13bd 100644
--- a/packages/flutter/test/widgets/fade_in_image_test.dart
+++ b/packages/flutter/test/widgets/fade_in_image_test.dart
@@ -198,6 +198,44 @@
       expect(parts.target.opacity, 1);
     });
 
+    // Regression test for https://github.com/flutter/flutter/issues/111011
+    testWidgets("FadeInImage's image obeys gapless playback when first image is cached but second isn't",
+            (WidgetTester tester) async {
+      final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage);
+      final TestImageProvider imageProvider = TestImageProvider(targetImage);
+      final TestImageProvider secondImageProvider = TestImageProvider(replacementImage);
+
+      // Pre-cache the initial image.
+      imageProvider.resolve(ImageConfiguration.empty);
+      imageProvider.complete();
+      placeholderProvider.complete();
+
+      await tester.pumpWidget(FadeInImage(
+        placeholder: placeholderProvider,
+        image: imageProvider,
+      ));
+      await tester.pumpAndSettle();
+
+      await tester.pumpWidget(FadeInImage(
+        placeholder: placeholderProvider,
+        image: secondImageProvider,
+      ));
+
+      FadeInImageParts parts = findFadeInImage(tester);
+      // Continually shows previously loaded image until the new image provider provides the image.
+      expect(parts.placeholder, isNull);
+      expect(parts.target.rawImage.image!.isCloneOf(targetImage), isTrue);
+      expect(parts.target.opacity, 1);
+
+      // Now, provide the image.
+      secondImageProvider.complete();
+      await tester.pump();
+
+      parts = findFadeInImage(tester);
+      expect(parts.target.rawImage.image!.isCloneOf(replacementImage), isTrue);
+      expect(parts.target.opacity, 1);
+    });
+
     testWidgets("FadeInImage's placeholder obeys gapless playback", (WidgetTester tester) async {
       final TestImageProvider placeholderProvider = TestImageProvider(placeholderImage);
       final TestImageProvider secondPlaceholderProvider = TestImageProvider(replacementImage);