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
SOLVED
The problem was that a StreamBuilder was listening to this stream:
FirebaseAuth.instance.userChanges()
causing a call tofetchImage
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 :)
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 :
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 yourUI
orstate management
files.I encourage you to read articles about "Clean Architecture" in Flutter like this one.