I want to upload image to firebase storage from a link using flutter so if anyone can tell me how can i do that.
Right now i am using imagePicker to pick images from mobile phone but now i wanted to upload the pics from URL i have searched for it and there is answer for javascript or other but not for flutter and i want it in flutter
Thanks
You have to first download the image from the link and save it in you temporary directory and then upload into firebase as you are already uploading it from image picker
here is the sample code that will help you to understand better
download(String url, String fileName) async {
try {
var per = await askPermission();
if (per!.isGranted) {
const path =
'/storage/emulated/0/Download/'; //you can use temporary
directory
final file = File('$path/$fileName');
await dio!.download(siteUrl + url, file.path,
onReceiveProgress: (rec, total) {
isLoading = true;
print(rec);
});
Get.snackbar('Success', 'File downloaded successfully',
backgroundColor: Colors.green.withOpacity(0.4));
} else {
Get.snackbar('Error', 'please grant storage permission',
backgroundColor: Colors.red.withOpacity(0.4));
}
} catch (e) {
pr.close();
Get.snackbar('Error', '${e.toString}',
backgroundColor: Colors.red.withOpacity(0.4));
print(e);
}
}
i use dio and Permission handler packages
from your linked Question, You can store the urls in your database else if you really want to store them in your firebase storage you have to download them and save to firebase. You can use plugins like flutter_downloader,
or use dio /http to download then upload to firebase
this has worked for me
`
class ImageUrlToStorage extends StatefulWidget {
const ImageUrlToStorage({Key key}) : super(key: key);
#override
State<ImageUrlToStorage> createState() => _ImageUrlToStorageState();
}
class _ImageUrlToStorageState extends State<ImageUrlToStorage> {
Dio dio;
bool isLoading = false;
final url =
'https://images.pexels.com/photos/733853/pexels-photo-733853.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500';
_save() async {
var status = await Permission.storage.request();
if (status.isGranted) {
var response = await Dio()
.get(url, options: Options(responseType: ResponseType.bytes));
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(response.data),
quality: 60,
name: "hello");
print(result);
}
}
#override
Widget build(BuildContext context) {
return ButtonWidget(
onClicked: () {
_save();
},
text: 'Upload Image to Storage',
);
}
}
`
I have used dio,image_gallery_saver,and Permission_handler
Related
I need to implement a deep link or referral system with my flutter application. The theory is
Singup and Signin will be handled by custom backend and not firebase
After a user signs up to my application he will be able to refer the app to others and if others install the app the referrer will gain some points.
Most work in this process will be handled by our custom backend. What I need is when someone uses my referral code I want that code during his/her signup.
So this is the service layer I created:
class DynamicLinkService {
final dynamicLink = FirebaseDynamicLinks.instance;
handleDynamicLink() async {
await dynamicLink.getInitialLink();
// dynamicLink.onLink(onSuccess: (PendingDynamicLinkData data) async {
// // something
// },
// onError: (OnLinkErrorException e) async {
// // something
// },
// );
}
Future<String> createDynamicLink() async {
User user = Store.instance.getUser();
String userId = user.id;
print("User id = $userId");
final DynamicLinkParameters dynamicLinkParameters = DynamicLinkParameters(
uriPrefix: 'https://shoppydev.page.link',
link: Uri.parse(
'https://shoppydev.page.link/?invitedBy=$userId',
),
androidParameters: AndroidParameters(
packageName: 'co.company.app',
minimumVersion: 0,
),
iosParameters: IOSParameters(
bundleId: 'co.company.app',
minimumVersion: '0.0.1',
),
socialMetaTagParameters: SocialMetaTagParameters(
title: 'Refer A friend',
description: 'Refer and earn points',
),
);
final ShortDynamicLink shortDynamicLink = await dynamicLink.buildShortLink(
dynamicLinkParameters,
);
final Uri dynamicUrl = shortDynamicLink.shortUrl;
print(dynamicUrl.toString());
return dynamicUrl.toString();
}
void handleSuccessfulLinking(PendingDynamicLinkData? data) async {
final Uri? deepLink = data!.link;
print(deepLink.toString());
if (deepLink != null) {
var isRefer = deepLink.toString().contains('invitedBy');
if (isRefer) {
var code = deepLink.toString().split('invitedBy=')[1];
print(code);
if (code != null) {
// code contains the referrer's user id
// signup with the referrer's id
}
}
}
}
}
As you can see I tried to create a unique referral link with the user id for now. But most guides I am following as well as some github repos did something like this for handling dynamic link:
dynamicLink.onLink(onSuccess: (PendingDynamicLinkData data) async {
// something
},
onError: (OnLinkErrorException e) async {
// something
},
);
Which throws: The expression doesn't evaluate to a function, so it can't be invoked.
Other notes that might help:
Inside my app.dart I have:
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
void initState() {
super.initState();
initDynamicLinks(context);
}
#override
Widget build(BuildContext context) {
final provider = Provider.of<LocaleProvider>(context);
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'App Name',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: buildRouter,
locale: provider.locale,
supportedLocales: L10n.all,
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
);
}
/*
Dynamic Links
*/
void initDynamicLinks(BuildContext context) async {
final PendingDynamicLinkData? data =
await FirebaseDynamicLinks.instance.getInitialLink();
final Uri? link = data?.link;
if (link != null) {
Navigator.pushNamed(context, link.path);
}
}
}
Issues I have faced till now:
I still haven't found a solid documentation on how to get the referral code(which is need for rewarding the referrer).
I have already checked out this two posts on stack:
Implementing referral rewards in Flutter
Flutter - How to pass custom arguments in firebase dynamic links for app invite feature?
In short, I want to create a unique refer link with my user id. Share the user id with someone else and when he/she registers to my app I want to get the referral code attached to the link.
Example: https://app.page.link/?invitedBy=$userId
When someone installs and registers I want the userId so I can pass it to the invitedBy property of SignUpRequest.
Edit: I think I didn't clarify my question enough. So I will set it up with an example:
I want an unique referral link on my phone which I can give to my friend John. And once he downloads and registers the app I want to get some reward points.
So when he sends his SignUpRequest to the Backend I want my referral code to go with that request, so the request will look like:
SignUpRequest()
..name = "John Doe",
..email = "john#gmail.com"
..invitedBy = "...my referral code goes here"
All the other validation and point giving process will be done in the BE
Put all of the below code in the App.dart or Splash screen, basically the first screen
initState
#override
void initState() {
super.initState();
_initDynamicLinks();
}
_initDynamicLinks - this is from where the dynamic link will be launched
Future<void> _initDynamicLinks() async {
final PendingDynamicLinkData data = await instance.getInitialLink();
final Uri deepLink = data?.link;
_handleDynamicLink(deepLink);
FirebaseDynamicLinks.instance.onLink.listen((dynamicLink) {
final uri = dynamicLink.link;
_handleDynamicLink(uri);
}).onError((e) {
print('onLinkError');
print(e.message);
});
}
_handleDynamicLink - this is where you handle the link and parse it
void _handleDynamicLink(Uri deepLink) async {
if (deepLink != null) {
final url = deepLink.toString();
var isRefer = url.contains('invitedBy');
if (isRefer) {
var code = url.split('invitedBy=')[1];
print(code);
if (code != null) {
// code contains the referrer's user id
// signup with the referrer's id
}
}
}
}
I think this way will be more clean
first add this widget
class DynamicLinksWidgetHandler extends StatefulWidget {
const DynamicLinksWidgetHandler({
super.key,
required this.child,
});
final Widget child;
#override
State<DynamicLinksWidgetHandler> createState() =>
_DynamicLinksWidgetHandlerState();
}
class _DynamicLinksWidgetHandlerState extends State<DynamicLinksWidgetHandler> {
#override
void initState() {
super.initState();
_initDynamicLinks();
}
// _initDynamicLinks - this is from where the dynamic link will be launched
Future<void> _initDynamicLinks() async {
final data = await FirebaseDynamicLinks.instance.getInitialLink();
final Uri? deepLink = data?.link;
_handleDynamicLink(deepLink);
FirebaseDynamicLinks.instance.onLink.listen((dynamicLink) {
final uri = dynamicLink.link;
_handleDynamicLink(uri);
}).onError((e) {
print('onLinkError');
print(e.message);
});
}
// _handleDynamicLink - this is where you handle the link and parse it
void _handleDynamicLink(Uri? deepLink) async {
log('_handleDynamicLink:$deepLink');
final code = deepLink?.queryParameters['invitedby'];
if (code == null) return;
// save code to backend
log(code);
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
and then wrap it on your app widget like this
runApp(
const DynamicLinksWidgetHandler(
child: MyApp(),
),
);
I have switcher function which I created in order to change state of my icon according to information taken from firebase. So, when
"isFavSalon" in switcher is true the icon should be filled else it should be not filled. isFavSalon must be getting from firebase.
The switcher:
initState() {
isFavSalonBlock = widget.isFavSalon;
print(isFavSalonBlock);
super.initState();
}
#override
Widget build(BuildContext context) {
void switcherIsFavSalon() async {
var firebaseUser = FirebaseAuth.instance.currentUser;
var docRef = FirebaseFirestore.instance
.collection('customers')
.doc(firebaseUser.uid)
.collection('favSalons');
if (isFavSalonBlock == false) {
setState(() {
favIconBlock = 'assets/icons/heart_filled.png';
isFavSalonBlock = true;
docRef.add({
"salonName": widget.salonName,
"workTime": widget.workTime,
"location": widget.location,
"rating": widget.rating,
"isFavSalon": isFavSalonBlock,
});
});
} else {
setState(() {
favIconBlock = 'assets/icons/heart_border.png';
isFavSalonBlock = false;
docRef.get().then((value) {
value.docs.forEach((element) async {
if (element.data()['salonName'] == widget.salonName)
await docRef.doc(element.id).delete();
});
});
});
}
}
favIcon in this switcher pass to the icon's image path.
Also, I have function which gets information from firebase and stores it in my Block.
The function:
getFavSalons(AsyncSnapshot<QuerySnapshot> snapshot) {
return snapshot.data.docs
.map((doc) => SalonBlock(
salonName: doc.data()["salonName"],
location: doc.data()["location"],
workTime: doc.data()["workTime"],
rating: doc.data()["rating"],
))
.toList();
}
But when I restart my app and switch between pages state of icons doesn't change according to information stored in firebase.
However when I press on the icon information is sending to firebase.
So how can I save the state in my app?
I've recently added Flutter Web support for one of my projects which heavily uses Firebase services. Everything seems to work fine except Firebase Storage which doesn't work. I know that firebase_storage plugin currently doesn't work with Web so I tried instead with the regular firebase plugin but I can't get it to work.
I sometimes get different "red screen"-errors, but everything is related to pages which uses Firebase Storage. Here is one example of a red screen:
Here are the 3 files in which I previously used firebase_storage with success (Android) and with the code that I tried to get to work with th firebase plugin.
import 'package:firebase/firebase.dart' as fb;
// artiklar/images
final fb.StorageReference fbRefArtiklarImages =
fb.app().storage().ref().child("artiklar").child("images");
// guider/categoryImages
final fb.StorageReference fbRefGuiderCategoryImages =
fb.app().storage().ref().child("guider").child("categoryImages");
// guider/guideImages
final fb.StorageReference fbRefGuiderGuideImages =
fb.app().storage().ref().child("guider").child("guideImages");
// kalender/images
final fb.StorageReference fbRefKalenderImages =
fb.app().storage().ref().child("kalender").child("images");
// sidor/sidloggor
final fb.StorageReference fbRefSidorSidloggorImages =
fb.app().storage().ref().child("sidor").child("sidloggor");
// sidor/sidcovers
final fb.StorageReference fbRefSidorSidcoversImages =
fb.app().storage().ref().child("sidor").child("sidcovers");
// sidor/postImages/:sidaID/
final fb.StorageReference fbRefSidorPostImagesImages =
fb.app().storage().ref().child("sidor").child("postImages");
// sidor/postImages/:sidaID/
final fb.StorageReference fbRefSidorKalenderImagesImages =
fb.app().storage().ref().child("sidor").child("kalenderImages");
-
import 'dart:io';
import 'package:firebase/firebase.dart' as fb;
class StorageService {
//STORAGE REFERENCES
final fb.Storage _storage = fb.app().storage("gs://astoria-site.appspot.com");
//UPLOADS IMAGE TO FIREBASE
fb.UploadTask _uploadTask;
Future<void> uploadStorageImage(File imageFile, String filePath) async {
_uploadTask = _storage.ref().child(filePath).put(imageFile);
return;
}
//DELETES IMAGE IN FIREBASE
Future<void> deleteStorageImage(String filePath) async {
try {
await _storage.ref().child(filePath).delete();
} catch (e) {
print(e.toString());
}
return;
}
}
-
import 'package:astoria/theme/colors.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:firebase/firebase.dart';
import 'package:flutter/material.dart';
class FirebaseStorageImage extends StatelessWidget {
final String fileName;
final StorageReference storageLocation;
FirebaseStorageImage({
#required this.fileName,
#required this.storageLocation,
});
Future<String> _getImageURL() async {
final StorageReference ref = storageLocation.child(fileName + ".jpg");
try {
var url = await ref.getDownloadURL();
return url.toString();
} catch (e) {
return null;
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getImageURL(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return Image(
image: CachedNetworkImageProvider(snapshot.data),
fit: BoxFit.cover,
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
//RETURN THIS WHILE WAITING FOR IMAGE
return Container(color: lightGreyColor);
} else {
//RETURN THIS IF NO IMAGE WAS FOUND AT THAT LOCATION
return Image(
image: AssetImage("assets/images/placeholder.png"),
fit: BoxFit.cover,
);
}
},
);
}
}
You need to pass a string to the ref method for it to work.
For ease, change ref to refFromURL, then pass your bucket URL as a string to it as follows;
fb.app().storage().refFromURL("YOUR BUCKET URL HERE eg: 'gs://project-ID.appspot.com'")
Then you can safely add the remaining methods to it, such as; child() then put() or putString().
Goodluck!!!
For those coming after me, here's a summary of achieving Firebase Storage image download for Flutter Web.
Thanks to Learn Flutter Code for this nice little tutorial.
Don't make Firebase Storage a dependency, just Firebase with:
import 'package:firebase/firebase.dart' as fb;
Then create a method:
Future<Uri> myDownloadURL() async {return await fb.storage().refFromURL('gs://<your storage reference>').child('$id.jpg').getDownloadURL();}
Call it from a FutureBuilder like so:
FutureBuilder<Uri>(
future: myDownloadURL(),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return <Something as a placeholder>;
}
return CircleAvatar(
radius: backgroundRadius * 2,
child: Image.network(snapshot.data.toString()),
);
},
)
I am having issues with Flutter web and uploading Images to Firestore. I'm pretty sure the issue lies in the Image Picker, as the normal(mobile) image picker does not work for the web. The normal image picker returns a File, but the alternative image_picker_web returns an Image, which gets rejected on upload because it's expecting a Future<File>.
image_picker_web has an alternative to return a Uint8List which I have used, and then converted to a File via dart:html - and uploads fine, but the image is corrupted and not viewable.
Here's what I have done:
On Button Press - Pick Image as Uint8List > Convert to Image, Store in memory and Display on Screen
onPressed: () async {
//Upload Image as Uint8List
imageBytes = await ImagePickerWeb.getImage(asUint8List: true);
//Convert Uint8List to Image
_image = Image.memory(imageBytes);
//Show new image on screen
setBottomSheetState(() {
image = _image;
});
},
Convert Uint8List to File using dart:html File and name as users UID.png (PNG Uploaded)
imageFile = html.File(imageBytes, '${user.uid}.png');
Use Method to upload File
import 'dart:async';
import 'package:firebase/firebase.dart' as fb;
import 'package:universal_html/prefer_universal/html.dart' as html;
String url;
Future<String> uploadProfilePhoto(html.File image, {String imageName}) async {
try {
//Upload Profile Photo
fb.StorageReference _storage = fb.storage().ref('profilephotos/$imageName.png');
fb.UploadTaskSnapshot uploadTaskSnapshot = await _storage.put(image).future;
// Wait until the file is uploaded then store the download url
var imageUri = await uploadTaskSnapshot.ref.getDownloadURL();
url = imageUri.toString();
} catch (e) {
print(e);
}
return url;
}
Call method
location = await uploadProfilePhoto(imageFile, imageName: '${user.uid}');
Add data including Location to Firebase Database
//Pass new user ID through to users Collection to link UserData to this user
await AdminUserData(uid: user.uid).updateAdminUserData(name: userName, email: userEmail, profilephoto: location);
Everything is working OK, just the image seems to be corrupted, it also comes back at almost double the filesize, which obviously means the File isn't coming back as the Image..
This is an old post but in case someone still needs help with this as I have been searching around for hours to figure this out. This is how I am doing it.
Import image_picker_web. I am using version 2.0.3.
Use ImagePickerWeb.getImageInfo on a button ontap listener to get the image info.
var fileInfo = await ImagePickerWeb.getImageInfo;
Show the image using Image.memory in the widget tree. (optional)
Image.memory(fileInfo.data!,width: 180),
Create firebase upload location
final firebasefileLocation = firebaseStorageLocation.child('${DateTime.now()}_${fireInfo.fileName}');
Upload the image to firebase.
await firebasefileLocation.putData(img.data!);
So this is how my file looks to work for both phone and web. There is more information about this and how to select multiple images on the image_picker_web page. You can use the concepts from here to make it crossed-platformed with IOS and Android too.
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_picker_web/image_picker_web.dart';
class ImagePickerDemo extends StatefulWidget {
const ImagePickerDemo({Key? key}) : super(key: key);
#override
_ImagePickerDemoState createState() => _ImagePickerDemoState();
}
class _ImagePickerDemoState extends State<ImagePickerDemo> {
MediaInfo? _imageInfo;
Future<void> _pickImage() async {
var fileInfo = await ImagePickerWeb.getImageInfo; //get image
if (fileInfo.data == null) return; // user did not choose image.
setState(() {
_imageInfo = fileInfo; // save image
});
}
Future<void> _uploadImage() async {
if (_imageInfo == null) return;
final firebaseStorageLocation =
FirebaseStorage.instance.ref().child('product_images');
final imageInfo = _imageInfo as MediaInfo;
_imageInfo as MediaInfo;
final firebasefileLocation = firebaseStorageLocation
.child('${DateTime.now()}_${imageInfo.fileName!}');
await firebasefileLocation.putData(imageInfo.data!);
final urlToUseLater = await firebasefileLocation.getDownloadURL();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: _pickImage, child: Text('Choose Image')),
ElevatedButton(
onPressed: _imageInfo == null ? null : _uploadImage,
child: Text('Upload Image')),
Image.memory(
_imageInfo!.data!,
width: 180,
)
],
);
}
}
I have not tried the alternatives you mentioned, but below has worked for me before on Flutter web and Firebase. The event listener for uploadInput works for most platforms. The last part regarding document.body.append will ensure that it works on Mobile safari as well.
Future<void> _setImage() async {
final completer = Completer<String>();
InputElement uploadInput = FileUploadInputElement();
uploadInput.multiple = false;
uploadInput.accept = 'image/*';
uploadInput.click();
uploadInput.addEventListener('change', (e) async {
// read file content as dataURL
final files = uploadInput.files;
Iterable<Future<String>> resultsFutures = files.map((file) {
final reader = FileReader();
reader.readAsDataUrl(file);
reader.onError.listen((error) => completer.completeError(error));
return reader.onLoad.first.then((_) => reader.result as String);
});
final results = await Future.wait(resultsFutures);
completer.complete(results[0]);
});
document.body.append(uploadInput);
final String image = await completer.future;
widget.newImage = uploadInput.files[0];
// Upload to Firebase
uploadToFirebase(widget.newImage); // This is dart:html File
uploadInput.remove();
}
Then the upload to Firebase Storage:
uploadToFirebase(String imageName, File file) async {
Firebase.UploadTask task = storage.refFromURL('gs://.../images/' + imageName).put(file);
}
For the underlying question of:
"How to upload image bytes to Firebase Storage?"
Here is a possible implementation:
import 'dart:developer';
import 'package:file_picker/file_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
/// Opens a file picker and uploads a single selected file to Firebase storage.
/// Returns a download URL if upload is successful or null if the operation is
/// aborted.
///
/// Throws an exception if more than one file is selected or the selected file
/// size exceeds 300KB
Future<String?> pickAndUploadFile() async {
final ref = FirebaseStorage.instance.refFromURL('gs://YOUR-PROJECT.appspot.com');
String? res;
final filePickerRes = await FilePicker.platform.pickFiles();
if (filePickerRes != null) {
if (filePickerRes.count == 1) {
final file = filePickerRes.files.single;
if (file.size > 300000) {
throw Exception('File must be less than 300KB');
}
final upTask = ref.child('uploads/${file.name}').putData(file.bytes!);
final snapshot = upTask.snapshot;
res = (await snapshot.ref.getDownloadURL()).toString();
} else {
throw Exception('only one file allowed');
}
}
log('downloadUrl: $res');
return res;
}
The result (snapshot.ref.getDownloadURL()) is a qualified URL you can use with any image widget that loads a URL.
How can I make a network image being available offline in my app? So, when I don't have network, it can be loaded from cache next time.
I'd tried network_to_file_image package and cache network image package as well, but it didn't work for me.
When I used cached network and I opened the app next time (without network) the images didn't load again from cache memory.
Here is the code where I use network image package in my Flutter app:
CarouselSlider(
enlargeCenterPage: true,
autoPlay: true,
height: 350,
initialPage: 0,
items: keyPlans.map((key) {
File file;
getData() {
DirServices().file("loacliamge_${counter++}").then((File data) {
file = data;
});
return file;
}
return new CarousalCard(
image: NetworkToFileImage(file: getData(), url: key, debug: true),
onPressed: () {
setState(() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ImageView(
image: CachedNetworkImageProvider(key),
text: appTitle,
)));
});
},
);
}).toList());
network_to_file_image package always fetch data from the network.
Temp solution for cache network image package.
Add IOFileSystem class to your project, as described here. (Hope this class will be available from package and we'll be able to remove this duplicated class in future)
import 'package:file/local.dart';
import 'package:flutter_cache_manager/src/storage/file_system/file_system.dart' as c;
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
class IOFileSystem implements c.FileSystem {
final Future<Directory> _fileDir;
IOFileSystem(String key) : _fileDir = createDirectory(key);
static Future<Directory> createDirectory(String key) async {
// use documents directory instead of temp
var baseDir = await getApplicationDocumentsDirectory();
var path = p.join(baseDir.path, key);
var fs = const LocalFileSystem();
var directory = fs.directory((path));
await directory.create(recursive: true);
return directory;
}
#override
Future<File> createFile(String name) async {
assert(name != null);
return (await _fileDir).childFile(name);
}
}
And then create CustomCacheManager
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
class CustomCacheManager extends CacheManager with ImageCacheManager {
static const String key = "customCache";
static CustomCacheManager _instance;
factory CustomCacheManager() {
return _instance ??= CustomCacheManager._();
}
CustomCacheManager._()
: super(Config(key, fileSystem: IOFileSystem(key)),);
}
Then finally provide this CustomCacheManager to the CachedNetworkImage
CachedNetworkImage(
cacheManager: CustomCacheManager(),
imageUrl: "your_image_url",
)
Then cached files won't be deleted by system between sessions