Skip to content

Undefined name after widget extraction #61546

@stephane-archer

Description

@stephane-archer
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lutme/adaptive_image.dart';
import 'package:lutme/ref_image_edited_before_lut_path_provider.dart';
import 'package:lutme/resolution.dart';

class RefImageEditedBeforeLutWidget extends ConsumerWidget {
  const RefImageEditedBeforeLutWidget({super.key});

  @override
  Widget build(final BuildContext context, final WidgetRef ref) {
    return LayoutBuilder(
      builder: (final BuildContext context, final BoxConstraints constraints) {
        // Calculate the maximum resolution based on the available space.
        final Resolution maxResolution =
            calculateMaxResolution(context, constraints, step: 200);

        // Define arguments for the low-resolution image provider.
        const lowRes = 512;
        final lowResArg = RefImageEditedBeforeLutPathArg.fromProviders(ref,
            resolution: const Resolution(lowRes, lowRes));

        // --- Step 1: Watch the low-resolution provider exclusively first. ---
        final lowResPathAsync =
            ref.watch(refImageEditedBeforeLutPathProvider(lowResArg));

        return lowResPathAsync.when(
          loading: () {
            // --- State: Initial loading state for low-res ---
            return const Image(image: AssetImage("assets/imgs/Spinner.gif"));
          },
          error: (error, stackTrace) {
            // --- State: Error handling for low-res ---
            return Center(
              child: Text(
                "Error loading image: $error",
                textAlign: TextAlign.center,
              ),
            );
          },
          data: (lowResPath) {
            if (lowResPath == null) {
              return const SizedBox.shrink();
            }

            // --- Step 2: Low-res is available. Now watch the high-res provider. ---
            final highResArg = RefImageEditedBeforeLutPathArg.fromProviders(ref,
                resolution: maxResolution);
            final highResPathAsync =
                ref.watch(refImageEditedBeforeLutPathProvider(highResArg));

            final highResPath = highResPathAsync.asData?.value;

            // --- The Solution: Use a Stack to prevent flickering ---
            // The low-resolution image acts as a persistent background,
            // ensuring something is always on screen. The high-resolution
            // image is then layered on top once it's fully loaded.
            return Stack(
              fit: StackFit.expand,
              alignment: Alignment.center,
              children: [
                // Layer 1: The low-resolution image is always visible.
                AdaptiveImageWithoutFadeIn(
                  FileImage(File(lowResPath)),
                  isPreview: false,
                ),

                // Layer 2: The high-resolution image appears on top when ready.
                // It's wrapped in an AnimatedOpacity for a smooth fade-in effect,
                // which is visually more appealing than an instant pop-in.
                AnimatedOpacity(
                  opacity: highResPath != null ? 1.0 : 0.0,
                  duration: const Duration(milliseconds: 300),
                  child: highResPath != null
                      ? AdaptiveImageWithoutFadeIn(
                          FileImage(File(highResPath)),
                          isPreview: false,
                        )
                      : const SizedBox.shrink(),
                ),

                // Layer 3: The loading indicator overlay.
                // This appears on top of the low-res image while the high-res is loading.
                if (highResPathAsync.isLoading)
                  const Center(
                    child: Tooltip(
                      message: "Loading higher quality preview",
                      child: CircularProgressIndicator(),
                    ),
                  ),
                
                // If high-res failed, we can optionally show an indicator or just log it.
                if (highResPathAsync.hasError)
                  // ignore: avoid_print
                  Builder(builder: (context) {
                    print("Failed to load high-res image, showing low-res instead: ${highResPathAsync.error}");
                    return const SizedBox.shrink();
                  },)
              ],
            );
          },
        );
      },
    );
  }
}

extract widget on the Stack

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lutme/adaptive_image.dart';
import 'package:lutme/ref_image_edited_before_lut_path_provider.dart';
import 'package:lutme/resolution.dart';

class RefImageEditedBeforeLutWidget extends ConsumerWidget {
  const RefImageEditedBeforeLutWidget({super.key});

  @override
  Widget build(final BuildContext context, final WidgetRef ref) {
    return LayoutBuilder(
      builder: (final BuildContext context, final BoxConstraints constraints) {
        // Calculate the maximum resolution based on the available space.
        final Resolution maxResolution =
            calculateMaxResolution(context, constraints, step: 200);

        // Define arguments for the low-resolution image provider.
        const lowRes = 512;
        final lowResArg = RefImageEditedBeforeLutPathArg.fromProviders(ref,
            resolution: const Resolution(lowRes, lowRes));

        // --- Step 1: Watch the low-resolution provider exclusively first. ---
        final lowResPathAsync =
            ref.watch(refImageEditedBeforeLutPathProvider(lowResArg));

        return lowResPathAsync.when(
          loading: () {
            // --- State: Initial loading state for low-res ---
            return const Image(image: AssetImage("assets/imgs/Spinner.gif"));
          },
          error: (error, stackTrace) {
            // --- State: Error handling for low-res ---
            return Center(
              child: Text(
                "Error loading image: $error",
                textAlign: TextAlign.center,
              ),
            );
          },
          data: (lowResPath) {
            if (lowResPath == null) {
              return const SizedBox.shrink();
            }

            // --- Step 2: Low-res is available. Now watch the high-res provider. ---
            final highResArg = RefImageEditedBeforeLutPathArg.fromProviders(ref,
                resolution: maxResolution);
            final highResPathAsync =
                ref.watch(refImageEditedBeforeLutPathProvider(highResArg));

            final highResPath = highResPathAsync.asData?.value;

            // --- The Solution: Use a Stack to prevent flickering ---
            // The low-resolution image acts as a persistent background,
            // ensuring something is always on screen. The high-resolution
            // image is then layered on top once it's fully loaded.
            return MyStack(highResPath: highResPath, highResPathAsync: highResPathAsync);
          },
        );
      },
    );
  }
}

class MyStack extends StatelessWidget {
  const MyStack({
    super.key,
    required this.highResPath,
    required this.highResPathAsync,
  });

  final String? highResPath;
  final AsyncValue<String?> highResPathAsync;

  @override
  Widget build(BuildContext context) {
    return Stack(
      fit: StackFit.expand,
      alignment: Alignment.center,
      children: [
        // Layer 1: The low-resolution image is always visible.
        AdaptiveImageWithoutFadeIn(
          FileImage(File(lowResPath)),
          isPreview: false,
        ),
    
        // Layer 2: The high-resolution image appears on top when ready.
        // It's wrapped in an AnimatedOpacity for a smooth fade-in effect,
        // which is visually more appealing than an instant pop-in.
        AnimatedOpacity(
          opacity: highResPath != null ? 1.0 : 0.0,
          duration: const Duration(milliseconds: 300),
          child: highResPath != null
              ? AdaptiveImageWithoutFadeIn(
                  FileImage(File(highResPath)),
                  isPreview: false,
                )
              : const SizedBox.shrink(),
        ),
    
        // Layer 3: The loading indicator overlay.
        // This appears on top of the low-res image while the high-res is loading.
        if (highResPathAsync.isLoading)
          const Center(
            child: Tooltip(
              message: "Loading higher quality preview",
              child: CircularProgressIndicator(),
            ),
          ),
        
        // If high-res failed, we can optionally show an indicator or just log it.
        if (highResPathAsync.hasError)
          // ignore: avoid_print
          Builder(builder: (context) {
            print("Failed to load high-res image, showing low-res instead: ${highResPathAsync.error}");
            return const SizedBox.shrink();
          },)
      ],
    );
  }
}

Undefined name 'lowResPath'. Try correcting the name to one that is defined, or defining the name.
The argument type 'String?' can't be assigned to the parameter type 'String'.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2A bug or feature request we're likely to work onarea-devexpFor issues related to the analysis server, IDE support, linter, `dart fix`, and diagnostic messages.devexp-refactoringIssues with analysis server refactoringstype-bugIncorrect behavior (everything from a crash to more subtle misbehavior)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions