skip to Main Content

my networkimage can read images from other links, but when it’s from firestore it can’t read, I don’t know why, I’ve already checked and the link that the console shows is working normally, the firebase rules are also public, I’m running out of options
code that shows the photo

usuario.isLoggedIn                   
? FutureBuilder<String?>(                  
     future: _loadUserAvatar(),                
       builder: (context, snapshot) {
                         if (snapshot.connectionState == ConnectionState.waiting) {
                           return CircleAvatar(
                             maxRadius: 40,
                             backgroundColor: Colors.white,
                             child: CircularProgressIndicator(
                               color: colorPrimary,
                             ),                           );
                         } else if (snapshot.hasData && snapshot.data != null) {
                           return CircleAvatar(
                             backgroundImage: NetworkImage(snapshot.data!),
                             maxRadius: 40,
                             backgroundColor: Colors.white,
                           );
                         } else {
                           return CircleAvatar(
                             backgroundImage: const AssetImage('icons/user.png'),
                             maxRadius: 40,
                             backgroundColor: Colors.white,
                           );
                         }
                       },
                     )
                   : CircleAvatar(
                       child: Icon(
                         Icons.person,
                         color: colorPrimary,
                         size: 50,
                       ),
                       maxRadius: 40,
                       backgroundColor: Colors.white,
                     ), 

error of log:

══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════

The following ProgressEvent object was thrown resolving an image codec:

[object ProgressEvent]

When the exception was thrown, this was the stack

Image provider:

NetworkImage("``https://firebasestorage.googleapis.com/v0/b/comp``...-8161d.firebasestorage.app/o/...",

scale: 1.0)

Image key:

NetworkImage("``https://firebasestorage.googleapis.com/v0/b/comp``...-8161d.firebasestorage.app/o/...",

scale: 1.0)

upload image code:

Future<String?> _uploadAvatarImage(Uint8List imageBytes) async {
 try {
    final img.Image? originalImage = img.decodeImage(imageBytes);
 if (originalImage == null) { throw Exception("Falha ao decodificar a imagem");
 } final img.Image resizedImage = img.copyResize(originalImage,
 width: 256,
 height: 256
);
 String fileExtension;
 String contentType;
 Uint8List resizedBytes;
if (imageBytes[0] == 0xFF && imageBytes[1] == 0xD8 && imageBytes[2] == 0xFF) {
  fileExtension = '.jpg';
  contentType = 'image/jpeg';
  resizedBytes = Uint8List.fromList(img.encodeJpg(resizedImage));
} else if (imageBytes[0] == 137 && imageBytes[1] == 80 && imageBytes[2] == 78) {
  fileExtension = '.png';
  contentType = 'image/png';
  resizedBytes = Uint8List.fromList(img.encodePng(resizedImage));
} else {
  throw Exception("Formato de imagem não suportado");
}
final String userId = FirebaseAuth.instance.currentUser!.uid;
final Reference storageRef = FirebaseStorage.instance
    .ref()
    .child("user_avatars/$userId$fileExtension");
final SettableMetadata metadata = SettableMetadata(contentType: contentType);
await storageRef.putData(resizedBytes, metadata);
final String imageUrl = await storageRef.getDownloadURL();

return imageUrl;
} catch (e, stacktrace) { print("Erro ao fazer upload da imagem: $e"); print("Stacktrace: $stacktrace");
 return null;
 }
 
}

i tried everything, reading the log is correct, I use it in the browser and the image appears perfectly, I’ve been stuck with this problem for 3 days, I tried to change the image’s metadata and it didn’t help, please someone help me

2

Answers


  1. Chosen as BEST ANSWER

    The problem was in Google Cloud's CORS, try setting cors.json to the following code:

    [
      {
        "origin": ["*"],
        "method": ["GET"],
        "maxAgeSeconds": 3600
      }
    ]

    I had difficulty changing CORS, directly in the Google Cloud console it didn't work, I had to download a tool called firebase admin, create a node project, download the json key from my firebase and run this code:

    const admin = require('firebase-admin');
    
    // Inicialize o Firebase Admin SDK com a chave privada
    const serviceAccount = require('./comprasnozapkey.json'); // Substitua pelo nome do seu arquivo de chave
    
    admin.initializeApp({
      credential: admin.credential.cert(serviceAccount),
      storageBucket: 'gs://yourbucket',  // Substitua pelo nome do seu bucket
    });
    
    const bucket = admin.storage().bucket();
    
    const corsConfig = [
      {
        origin: ['*'], // Substitua "*" pelo domínio do seu app para maior segurança
        method: ['GET'],
        maxAgeSeconds: 3600,
      },
    ];
    
    bucket.setCorsConfiguration(corsConfig)
      .then(() => {
        console.log('Configuração de CORS aplicada com sucesso!');
      })
      .catch((err) => {
        console.error('Erro ao configurar CORS:', err);
      });

    you have to have node npm, you have to have firebase sdk and firebase admin sdk.

    I don't remember exactly what the commands were but I remember that I just ran this code. Another quick but not recommended alternative that I found was to use the HTML renderer, but it is slow and not worth it for anyone who is actually creating a website, just for testing, etc. To use the renderer, just use this command in the terminal:

    for run:

    flutter run -d chrome --web-renderer html
    

    for build:

    flutter build web --release --web-renderer=html
    

    Translate the commented lines into Brazilian Portuguese, as I am Brazilian, thanks


  2. Salve bro,

    I have a reusable PickImage class in my app that I use. It might be overkill but I’ll share it with you.

      class PickImage {
      factory PickImage() => _internal;
    
      PickImage._();
    
      static final PickImage _internal = PickImage._();
    
      late TabsTexts _tabsTexts;
    
      void init({TabsTexts? tabsTexts}) {
        _tabsTexts = tabsTexts ?? const TabsTexts();
      }
    
      static final _defaultFilterOption = FilterOptionGroup(
        videoOption: FilterOption(
          durationConstraint: DurationConstraint(max: 3.minutes),
        ),
      );
    
      AppTheme _appTheme(BuildContext context) => AppTheme(
            focusColor: context.adaptiveColor,
            primaryColor: context.customReversedAdaptiveColor(),
          );
    
      SliverGridDelegateWithFixedCrossAxisCount _sliverGridDelegate() =>
          const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 4,
            crossAxisSpacing: 1.7,
            mainAxisSpacing: 1.5,
          );
    
      Future<void> pickImagesAndVideos(
        BuildContext context, {
        required Future<void> Function(
          BuildContext context,
          SelectedImagesDetails,
        ) onMediaPicked,
        bool cropImage = true,
        bool showPreview = true,
        int maxSelection = 10,
        bool multiSelection = true,
      }) =>
          context.pickBoth(
            source: ImageSource.both,
            multiSelection: multiSelection,
            filterOption: _defaultFilterOption,
            galleryDisplaySettings: GalleryDisplaySettings(
              maximumSelection: maxSelection,
              showImagePreview: showPreview,
              cropImage: cropImage,
              tabsTexts: _tabsTexts,
              appTheme: _appTheme(context),
              callbackFunction: (details) => onMediaPicked.call(context, details),
            ),
          );
    
      Future<SelectedImagesDetails?> pickImage(
        BuildContext context, {
        ImageSource source = ImageSource.gallery,
        int maxSelection = 1,
        bool cropImage = true,
        bool multiImages = false,
        bool showPreview = true,
        bool pickAvatar = false,
      }) =>
          context.pickImage(
            source: source,
            multiImages: multiImages,
            filterOption: _defaultFilterOption,
            galleryDisplaySettings: GalleryDisplaySettings(
              cropImage: cropImage,
              maximumSelection: maxSelection,
              showImagePreview: showPreview,
              tabsTexts: _tabsTexts,
              pickAvatar: pickAvatar,
              appTheme: _appTheme(context),
              gridDelegate: _sliverGridDelegate(),
            ),
          );
    
      Future<void> pickVideo(
        BuildContext context, {
        required Future<void> Function(
          BuildContext context,
          SelectedImagesDetails,
        ) onMediaPicked,
        ImageSource source = ImageSource.both,
        int maxSelection = 10,
        bool cropImage = true,
        bool multiImages = false,
        bool showPreview = true,
      }) =>
          context.pickVideo(
            source: source,
            filterOption: _defaultFilterOption,
            galleryDisplaySettings: GalleryDisplaySettings(
              showImagePreview: showPreview,
              cropImage: cropImage,
              maximumSelection: maxSelection,
              tabsTexts: _tabsTexts,
              appTheme: _appTheme(context),
              callbackFunction: (details) => onMediaPicked.call(context, details),
            ),
          );
    
      Widget customMediaPicker({
        required BuildContext context,
        required ImageSource source,
        required PickerSource pickerSource,
        required ValueSetter<SelectedImagesDetails> onMediaPicked,
        Key? key,
        bool multiSelection = true,
        FilterOptionGroup? filterOption,
        VoidCallback? onBackButtonTap,
      }) =>
          CustomImagePicker(
            key: key,
            galleryDisplaySettings: GalleryDisplaySettings(
              showImagePreview: true,
              cropImage: true,
              tabsTexts: _tabsTexts,
              appTheme: _appTheme(context),
              callbackFunction: (details) async => onMediaPicked.call(details),
            ),
            multiSelection: multiSelection,
            pickerSource: pickerSource,
            source: source,
            filterOption: _defaultFilterOption,
            onBackButtonTap: onBackButtonTap,
          );
    
      Future<Uint8List> imageBytes({required File file}) =>
          compute((file) => file.readAsBytes(), file);
    }
    

    And then I make another class like this

    typedef UserProfilePlaceholderBuilder = Widget Function(
      BuildContext context,
      String url,
    );
    
    class UserProfileAvatar extends StatelessWidget {
      const UserProfileAvatar({
        this.userId,
        super.key,
        this.avatarUrl,
        this.avatarFile,
        this.radius,
        this.isLarge = true,
        this.onTapPickImage = false,
        this.strokeWidth,
        this.onTap,
        this.onLongPress,
        this.onImagePick,
        this.animationEffect = TappableAnimationEffect.none,
        this.scaleStrength = ScaleStrength.xs,
        this.withAddButton = false,
        this.withEditButton = false,
        this.enableBorder = true,
        this.enableInactiveBorder = false,
        this.withShimmerPlaceholder = false,
        this.placeholderBuilder,
        this.onAddButtonTap,
        this.onEditButtonTap,
        this.withAdaptiveBorder = false,
      });
    
      final String? userId;
      final String? avatarUrl;
      final File? avatarFile;
      final double? radius;
      final double? strokeWidth;
      final bool isLarge;
      final bool onTapPickImage;
      final bool withShimmerPlaceholder;
      final ValueSetter<String?>? onTap;
      final ValueSetter<String?>? onLongPress;
      final VoidCallback? onAddButtonTap;
      final VoidCallback? onEditButtonTap;
      final ValueSetter<File>? onImagePick;
      final TappableAnimationEffect animationEffect;
      final ScaleStrength scaleStrength;
      final bool withAddButton;
      final bool withEditButton;
      final bool enableBorder;
      final bool enableInactiveBorder;
      final UserProfilePlaceholderBuilder? placeholderBuilder;
      final bool withAdaptiveBorder;
    
      static Widget _defaultPlaceholder({
        required BuildContext context,
        required double radius,
      }) =>
          CircleAvatar(
            backgroundColor: SurfColors.grey,
            radius: radius,
          );
    
      static const _defaultGradient = SweepGradient(
        colors: SurfColors.primaryGradient,
        stops: [0.0, 0.25, 0.5, 0.75, 1.0],
      );
    
      static const _gradientBorderDecoration = BoxDecoration(
        shape: BoxShape.circle,
        gradient: _defaultGradient,
      );
    
      static const _blackBorderDecoration = BoxDecoration(
        shape: BoxShape.circle,
        border: Border.fromBorderSide(BorderSide(width: 3)),
      );
    
      BoxDecoration _greyBorderDecoration(BuildContext context) => BoxDecoration(
            shape: BoxShape.circle,
            border: Border.fromBorderSide(
              BorderSide(
                color: context.customReversedAdaptiveColor(
                  dark: Colors.grey.shade800,
                  light: Colors.grey.shade400,
                ),
              ),
            ),
          );
    
      // TODO move to application layer
      Future<void> _pickImage(BuildContext context) async {
      
        try {
          Future<void> precacheAvatarUrl(String url) =>
              precacheImage(CachedNetworkImageProvider(url), context);
    
          final imageFile = await PickImage()
              .pickImage(context, source: ImageSource.both, pickAvatar: true);
          if (imageFile == null) return;
          final selectedFile = imageFile.selectedFiles.firstOrNull;
          if (selectedFile == null) return;
          final compressed =
              await ImageCompress.compressFile(selectedFile.selectedFile);
          final compressedFile = compressed == null ? null : File(compressed.path);
          final file = compressedFile ?? selectedFile.selectedFile;
        '${DateTime.now().toIso8601String()}.$fileExt';
      imageRepository.uploadUserImageFromFile(
      
    
          onImagePick?.call(file);
        } catch (error, stackTrace) {
          logE(
            'Failed to precache avatar url',
            error: error,
            stackTrace: stackTrace,
          );
        }
      }
    
      @override
      Widget build(BuildContext context) {
        final radius = (this.radius) ??
            (isLarge
                ? 42.0
                : withAdaptiveBorder
                    ? 22.0
                    : 18.0);
        late final height = radius * 2;
        late final width = radius * 2;
    
        BoxDecoration? border() {
        
          return null;
        }
    
        Gradient? gradient() {
        
          return null;
        }
    
        late Widget avatar;
    
        Widget placeholder(BuildContext context, String url) =>
            withShimmerPlaceholder
                ? ShimmerPlaceholder(radius: radius)
                : placeholderBuilder?.call(context, url) ??
                    _defaultPlaceholder(
                      context: context,
                      radius: radius,
                    );
    
    
        if (avatarFile != null ||
            avatarUrl == null ||
            (avatarUrl?.trim().isEmpty ?? true)) {
          final circleAvatar = CircleAvatar(
            radius: radius,
            backgroundColor: SurfColors.greyTextFieldFill,
            foregroundImage: avatarFile != null
                ? FileImage(avatarFile!)
                : Assets.images.profilePhoto.provider(),
          );
          if (!withAdaptiveBorder) {
            avatar = GradientCircleContainer(
              strokeWidth: strokeWidth ?? 2,
              radius: radius,
              gradient: gradient(),
              child: circleAvatar,
            );
          } else {
            avatar = Container(
              height: height + 12,
              width: width + 12,
              decoration: border(),
              child: Stack(
                alignment: Alignment.center,
                children: [
                  Container(
                    decoration: border() != null ? _blackBorderDecoration : null,
                    child: circleAvatar,
                  ),
                ],
              ),
            );
          }
        } else {
          final image = CachedNetworkImage(
            imageUrl: avatarUrl!,
            fit: BoxFit.cover,
            cacheKey: avatarUrl,
            height: height,
            width: width,
            memCacheHeight: height.toInt(),
            memCacheWidth: width.toInt(),
            placeholder: placeholder,
            errorWidget: (_, __, ___) => CircleAvatar(
              backgroundColor: SurfColors.white,
              radius: radius,
              foregroundImage: Assets.images.profilePhoto.provider(),
            ),
            imageBuilder: (context, imageProvider) => CircleAvatar(
              radius: radius,
              backgroundImage: imageProvider,
            ),
          );
          if (!withAdaptiveBorder) {
            avatar = GradientCircleContainer(
              strokeWidth: strokeWidth ?? 2,
              radius: radius,
              gradient: gradient(),
              child: image,
            );
          } else {
            avatar = Container(
              height: height + 12,
              width: width + 12,
              decoration: border(),
              child: Stack(
                alignment: Alignment.center,
                children: [
                  Container(
                    decoration: border() != null ? _blackBorderDecoration : null,
                    child: image,
                  ),
                ],
              ),
            );
          }
        }
    
        if (withAddButton) {
          final plusCircularIcon = Positioned(
            bottom: 0,
            right: 0,
            child: Tappable(
              onTap: onAddButtonTap,
              animationEffect: TappableAnimationEffect.scale,
              child: Container(
                width: isLarge ? 32 : 18,
                height: isLarge ? 32 : 18,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  shape: BoxShape.circle,
                  border: Border.all(
                    width: isLarge ? 3 : 2,
                    color: context.reversedAdaptiveColor,
                  ),
                ),
                child: Icon(
                  Icons.add,
                  size: isLarge ? Sizes.smallIconSize : Sizes.tinyIconSize,
                ),
              ),
            ),
          );
          avatar = Stack(children: [avatar, plusCircularIcon]);
        }
    
        if (withEditButton) {
          final plusCircularIcon = Positioned(
            bottom: 0,
            right: 0,
            child: Tappable(
              onTap: onEditButtonTap,
              animationEffect: TappableAnimationEffect.scale,
              child: Container(
                width: isLarge ? 32 : 18,
                height: isLarge ? 32 : 18,
                decoration: BoxDecoration(
                  color: SurfColors.primaryTeal,
                  shape: BoxShape.circle,
                  border: Border.all(
                    width: isLarge ? 3 : 2,
                    color: context.reversedAdaptiveColor,
                  ),
                ),
                child: Icon(
                  Icons.edit,
                  size: isLarge ? Sizes.smallIconSize : Sizes.tinyIconSize,
                ),
              ),
            ),
          );
          avatar = Stack(children: [avatar, plusCircularIcon]);
        }
        return Tappable(
          onTap: onTap == null
              ? !onTapPickImage
                  ? null
                  : () => _pickImage.call(context)
              : () => onTap?.call(avatarUrl),
          onLongPress:
              onLongPress == null ? null : () => onLongPress?.call(avatarUrl),
          animationEffect: animationEffect,
          scaleStrength: scaleStrength,
          child: avatar,
        );
      }
    }
    

    Then you can call it in your UI and add any customization you want, like

     UserProfileAvatar(
                 avatarUrl: user!.profileImageUrl,
                 radius: Sizes.extraLargeAvatarIconSize * sh * 0.0012)
    

    Let me know if that helps at all.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search