skip to Main Content

I am creating a kind of social app for school notes and I have added some features in order to allow users to customise their account. One of these is the profile picture, which I store inside Firebase Storage. Everything seems to work until I try to delete the picture from the storage, at that point the pictures disappear both from the storage and app, but an exception is launched.

import 'dart:io';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:minetry/widgets/my_dialog.dart';

class AvatarStorageService {
  // get image from storage
  static Future<String?> fetchImage(dynamic context) async {
    final firebaseStorage = FirebaseStorage.instance;
    String? userAvatarURL = FirebaseAuth.instance.currentUser!.photoURL;

    if (userAvatarURL == null) return null;

    final url =
        await firebaseStorage.ref().child(userAvatarURL).getDownloadURL();

    return url;
  }

  // delete image
  static Future<void> deleteImage(dynamic context) async {
    final firebaseStorage = FirebaseStorage.instance;
    String? userAvatarURL = FirebaseAuth.instance.currentUser!.photoURL;

    // show loading
    showDialog(
      barrierDismissible: false,
      context: context,
      builder: (context) => Center(
        child: CircularProgressIndicator(
          color: Theme.of(context).primaryColor,
        ),
      ),
    );

    if (userAvatarURL == null) {
      // pop loading
      Navigator.of(context).pop();

      showDialog(
        context: context,
        builder: (context) => const MyDialog(error: "Nessun avatar"),
      );

      return;
    }

    try {
      await firebaseStorage.ref(userAvatarURL).delete();

      await FirebaseAuth.instance.currentUser!.updatePhotoURL(null);

      // pop loading
      Navigator.of(context).pop();
      // go back
      Navigator.of(context).pop("bar");

      // show confirmation message
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          backgroundColor: Color.fromRGBO(17, 119, 12, 0.8),
          content: Center(
            child: Text(
              'Foto profilo eliminata',
              style: TextStyle(
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),
      );
    } on FirebaseAuthException {
      // pop loading
      Navigator.of(context).pop();

      showDialog(
          context: context,
          builder: (context) => const MyDialog(error: "Errore"));
    } catch (e) {
      // pop loading
      Navigator.of(context).pop();

      showDialog(
          context: context,
          builder: (context) => const MyDialog(error: "Errore"));
    }
  }

  // upload image
  static Future<void> uploadImage(dynamic context) async {
    final firebaseStorage = FirebaseStorage.instance;
    String uid = FirebaseAuth.instance.currentUser!.uid;

    // show loading
    showDialog(
      barrierDismissible: false,
      context: context,
      builder: (context) => Center(
        child: CircularProgressIndicator(
          color: Theme.of(context).primaryColor,
        ),
      ),
    );

    // get an image from phone
    final ImagePicker picker = ImagePicker();
    final XFile? image = await picker.pickImage(source: ImageSource.gallery);

    // if user cancels
    if (image == null) {
      // pop loading
      Navigator.of(context).pop();

      return;
    }

    File file = File(image.path);

    try {
      // create file path in storage
      String filePath = "avatars/$uid.png";

      // add picture to the storage
      await firebaseStorage.ref().child(filePath).putFile(file);

      // store the path inside user.PhotoURL
      await FirebaseAuth.instance.currentUser!.updatePhotoURL(filePath);

      // pop loading
      Navigator.of(context).pop();
      // go back (message is for refreshing purposes)
      Navigator.of(context).pop("bar");

      // show confirmation message
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          backgroundColor: Color.fromRGBO(17, 119, 12, 0.8),
          content: Center(
            child: Text(
              'Foto profilo cambiata',
              style: TextStyle(
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),
      );
    } on FirebaseAuthException catch (e) {
      if (FirebaseAuth.instance.currentUser != null) {
        // pop loading
        Navigator.of(context).pop();

        showDialog(
          context: context,
          builder: (context) => MyDialog(error: e.code),
        );
      }
    } catch (e) {
      if (FirebaseAuth.instance.currentUser != null) {
        // pop loading
        Navigator.of(context).pop();

        showDialog(
          context: context,
          builder: (context) => const MyDialog(error: "Errore"),
        );
      }
    }
  }
}

That dynamic context is to overcome the fact that BuildContext is not accepted inside async functions (If you guys have any suggestions on this I will accept them as well, thanks)

This is the entire class but I am having problems with deleteImage(dynamic context), more specifically, with this line of code:
await firebaseStorage.ref(userAvatarURL).delete();
I have tried to comment it and everything works fine.

Other things I have done are:
Writing manually the path inside (gives an exception)
Uploading manually from Firebase the image in the storage and deleting it with the code (This did not trow an exception, not sure if it is a coincidence)

The exception thrown is the following:

E/StorageException(16207): StorageException has occurred.
E/StorageException(16207): Object does not exist at location.
E/StorageException(16207): Code: -13010 HttpResult: 404
E/StorageException(16207): { "error": { "code": 404, "message": "Not Found." }}
E/StorageException(16207): java.io.IOException: { "error": { "code": 404, "message": "Not Found." }}
E/StorageException(16207): at com.google.firebase.storage.network.NetworkRequest.parseResponse(NetworkRequest.java:415)
E/StorageException(16207): at com.google.firebase.storage.network.NetworkRequest.parseErrorResponse(NetworkRequest.java:432)
E/StorageException(16207): at com.google.firebase.storage.network.NetworkRequest.processResponseStream(NetworkRequest.java:423)
E/StorageException(16207): at com.google.firebase.storage.network.NetworkRequest.performRequest(NetworkRequest.java:265)
E/StorageException(16207): at com.google.firebase.storage.network.NetworkRequest.performRequest(NetworkRequest.java:282)
E/StorageException(16207): at com.google.firebase.storage.internal.ExponentialBackoffSender.sendWithExponentialBackoff(ExponentialBackoffSender.java:76)
E/StorageException(16207): at com.google.firebase.storage.internal.ExponentialBackoffSender.sendWithExponentialBackoff(ExponentialBackoffSender.java:68)
E/StorageException(16207): at com.google.firebase.storage.GetDownloadUrlTask.run(GetDownloadUrlTask.java:77)
E/StorageException(16207): at com.google.firebase.concurrent.LimitedConcurrencyExecutor.lambda$decorate$0$com-google-firebase-concurrent-LimitedConcurrencyExecutor(LimitedConcurrencyExecutor.java:65)
E/StorageException(16207): at com.google.firebase.concurrent.LimitedConcurrencyExecutor$$ExternalSyntheticLambda0.run(Unknown Source:4)
E/StorageException(16207): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
E/StorageException(16207): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
E/StorageException(16207): at com.google.firebase.concurrent.CustomThreadFactory.lambda$newThread$0$com-google-firebase-concurrent-CustomThreadFactory(CustomThreadFactory.java:47)
E/StorageException(16207): at com.google.firebase.concurrent.CustomThreadFactory$$ExternalSyntheticLambda0.run(Unknown Source:4)
E/StorageException(16207): at java.lang.Thread.run(Thread.java:1012)

All of this shows up 2 or 3 times.

I found here on StackOverflow and GitHub some similar problems but in different situations, like the same exception but while uploading images on the storage (It was about reference but here the image is deleted ‘correctly’ so I don’t know)

Here’s my flutter doctor -v

[√] Flutter (Channel stable, 3.22.2, on Microsoft Windows [Versione 10.0.19045.4651], locale it-IT)
• Flutter version 3.22.2 on channel stable at C:Flutterflutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 761747bfc5 (4 months ago), 2024-06-05 22:15:13 +0200
• Engine revision edd8546116
• Dart version 3.4.3
• DevTools version 2.34.3

[√] Windows Version (Installed version of Windows is version 10 or higher)

[√] Android toolchain – develop for Android devices (Android SDK version 35.0.0)
• Android SDK at C:UsersnicolAppDataLocalAndroidsdk
• Platform android-35, build-tools 35.0.0
• Java binary at: C:Program FilesAndroidAndroid Studiojbrbinjava
• Java version OpenJDK Runtime Environment (build 17.0.10+0–11609105)
• All Android licenses accepted.

[√] Chrome – develop for the web
• Chrome at C:Program FilesGoogleChromeApplicationchrome.exe

[!] Visual Studio – develop Windows apps (Visual Studio Community 2022 17.3.1)
• Visual Studio at C:Program FilesMicrosoft Visual Studio2022Community
• Visual Studio Community 2022 version 17.3.32811.315
X Visual Studio is missing necessary components. Please re-run the Visual Studio installer for the "Desktop development with
C++" workload, and include these components:
MSVC v142 – VS 2019 C++ x64/x86 build tools
– If there are multiple build tool versions available, install the latest
C++ CMake tools for Windows
Windows 10 SDK

[√] Android Studio (version 2024.1)
• Android Studio at C:Program FilesAndroidAndroid Studio
• Flutter plugin can be installed from:
https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 17.0.10+0–11609105)

[√] VS Code (version 1.93.1)
• VS Code at C:UsersnicolAppDataLocalProgramsMicrosoft VS Code
• Flutter extension version 3.98.0

[√] Connected device (4 available)
• sdk gphone64 x86 64 (mobile) • emulator-5554 • android-x64 • Android 15 (API 35) (emulator)
• Windows (desktop) • windows • windows-x64 • Microsoft Windows [Versione 10.0.19045.4651] • Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.128
• Edge (web) • edge • web-javascript • Microsoft Edge 128.0.2739.67

[√] Network resources
• All expected network resources are available.

! Doctor found issues in 1 category.

If there are some english wrong terms or some issues with my code I’m sorry, this is my first project, thank you guys.

I was expecting to get the picture deleted without any exceptions, I tried to add .onError() and catchError() methods at the end of this line:
await firebaseStorage.ref(userAvatarURL).delete();
but they didn’t work at all.
I also tried to include that line inside an another try {} catch {} block but nothing stopped that exception.
As I said, the image is deleted (apparently in a correct way but there is something wrong down the hood)

2

Answers


  1. Chosen as BEST ANSWER

    SOLVED

    The problem was that a StreamBuilder was listening to this stream: FirebaseAuth.instance.userChanges() causing a call to fetchImage before uploading user's Url and setting it to null, hence this check: if (userAvatarURL == null) return null was being bypassed ( Url was being set to null a few milliseconds after the call) causing a StorageException.

    This is, roughly, what @Tanguy P said, therefore, I solved the issue thanks to him. Hope this can help :)


  2. My guess is that your deleteImage function is called more than one time by the rest of your code. That way, the first time it works as expected but the other times it throws because it’s already deleted.

    To counter that you have to :

    1. Verify that the file exists before trying to delete it. (As a matter of fact, you should always check that a resource exists before using it)
    2. Debug your code to prevent duplicate calls to deleteImage.

    About your dynamic parameter : creating a special class for resource management is a great way to separate concerns but you did it poorly.
    AvatarStorageService should be a pure Dart class and shouldn’t contain ANY reference to Flutter calls.

    So move all your logic about Dialog back to your UI or state management files.
    I encourage you to read articles about "Clean Architecture" in Flutter like this one.

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