Save network images in Flutter to load them offline - firebase

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

Related

flutter WebView lose pseudo of my chat page after each open app

I have a webview on a Pageview who display a chat from a plugin that I use on my wordpress website (I have no access to data from this plugin). It's not a chat with FB or google account, it's only an open chat room, where users can add and save her nickname (I suppose nickname is stored in cookies ?). As long as the webview is active the nickname remains memorized. Problem, after each time the app is close and reopen, the user lose his nickname.
Here is my code
WebView(
initialUrl: 'https://XXXX',
javascriptMode: JavascriptMode.unrestricted,
gestureRecognizers: [
Factory(() => PlatformViewVerticalGestureRecognizer()),
].toSet(),
),
How can I save session ? Even when after app is close and reopen ?
First, in your website project, add this javascript code which it will be accessible to the HTML pseodo input:
var psuedoInput = document.querySelector('inputSelectorHere');
_selector.addEventListener('change', function(event) {
var message = psuedoInput.value;
if (messageHandler) {
messageHandler.postMessage(message);
}
});
you can add it inside a <script></script> in the .html file or in a .js separate file.
this basically will post a message with the pseudo input value to our app later.
Don't forget to change inputSelectorHere with your psuedo input selector.
now in your flutter code, create a simple Stirng variable like this:
String? cookie;
then in the WebView widget:
WebView(
javascriptChannels: <JavascriptChannel>[
// javascript channel that saves the cookie
JavascriptChannel(
name: 'Cookie',
onMessageReceived: (JavascriptMessage message) {
cookie = message.message;
print("cookie: $cookie");
},
),
].toSet(),
onWebViewCreated: (controller) {
if (cookie == null) {
return;
}
controller.runJavascript("document.cookie = '$cookie';");
// }
},
initialCookies: [],
initialUrl: 'https://XXXX',
javascriptMode: JavascriptMode.unrestricted,
),
here the JavascriptChannel is set so it receives those messages which will be sent from your website from the webview, then it will be saved inside the cookie variable which we created.
when you close the webview and open it again, the onWebViewCreated will be called, and the cookie now is not null, so it will assign the cookie we saved to document.cookie in the webview.
As I can understand. You just need to get cookies (or cache and local storage) and store them in FlutterSecureStorage. when the user closes the app and re-opens just check if cookies are stored in FlutterSecureStorage or not.
If Cookies are present just add the cookies and refresh the page. I have written a pseudo code for the demo purpose (Code might not work as you expected but it will give you a brief idea about my approach).
I have added a code for the cookies. I also added code for the cache and local storage but you have to configure it according to your needs.
Please read the comments.
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:webview_flutter/webview_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const FlutterWebViewDemo());
}
class FlutterWebViewDemo extends StatefulWidget {
const FlutterWebViewDemo({Key? key}) : super(key: key);
#override
State<FlutterWebViewDemo> createState() => _FlutterWebViewDemoState();
}
class _FlutterWebViewDemoState extends State<FlutterWebViewDemo> {
late final WebViewCookieManager cookieManager = WebViewCookieManager();
var controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// Update loading bar.
},
onPageStarted: (String url) {},
onPageFinished: (String url) {},
onWebResourceError: (WebResourceError error) {},
),
)
..loadRequest(Uri.parse(''));
/// <---- please add the url here
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: [
Expanded(child: WebViewWidget(controller: controller)),
ElevatedButton(
onPressed: () async {
_onListCache();
},
child: const Text("Save Cache"),
),
ElevatedButton(
onPressed: () async {
_onAddToCache();
},
child: const Text("Set Cache"),
),
ElevatedButton(
onPressed: () async {
_onClearCache();
},
child: const Text("Clear Cache"),
),
ElevatedButton(
onPressed: () async {
_onListCookies();
},
child: const Text("Save Cookies"),
),
ElevatedButton(
onPressed: () async {
_onSetCookie();
},
child: const Text("Set Cookies"),
),
ElevatedButton(
onPressed: () async {
_onClearCookies();
},
child: const Text("Clear Cookies"),
)
],
),
),
);
}
Future<void> _onListCookies() async {
final String cookies = await controller
.runJavaScriptReturningResult('document.cookie') as String;
FlutterSecureStorage secureStorage = const FlutterSecureStorage();
secureStorage.write(key: 'cookies', value: cookies);
}
Future<void> _onSetCookie() async {
FlutterSecureStorage secureStorage = const FlutterSecureStorage();
String? cookies = await secureStorage.read(key: 'cookies');
/// get cookies from flutter secure storage and set them and refresh the page with new cookies.
/// please fill the required fields.
await cookieManager.setCookie(
WebViewCookie(
name: '',
/// required you have to set this
value: cookies!,
domain: '',
/// required
path: '/',
/// required
),
);
/// this will load the new page
await controller.loadRequest(Uri.parse(
'',
/// <---- refresh url
));
}
Future<void> _onClearCookies() async {
final bool hadCookies = await cookieManager.clearCookies();
String message = 'There were cookies. Now, they are gone!';
if (!hadCookies) {
message = 'There are no cookies.';
}
print(">>>>>>>>>> message $message");
}
Future<void> _onAddToCache() async {
/// <--- you have to write the logic to add cache and local storage from flutter secure storage. like this and refresh the page.
await controller.runJavaScript(
'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";',
);
}
Future _onListCache() async {
await controller.runJavaScriptReturningResult('caches.keys()');
/// <--- get cache and local storage and save it in flutter secure storage.
}
Future<void> _onClearCache() async {
await controller.clearCache();
await controller.clearLocalStorage();
}
}

Upload image from URL to Firebase Storage using flutter

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

Correct order to upload images on firebase

To upload images for an object created by a user I store the images (selected by the user) in an array 'imagesList' as a File. When the user clicked upload (whole object) the following method saves the data on firebase:
TextButton(
onPressed: () async {
await uploadImage();
await jobService.createJob(Job(
titleTextEditorController.text.trim(),
category,
false,
false,
finalImageList));
},
child: Text('upload')),
The List finalImageList is filled in the first method 'uploadImage()'. I sourced it out in another method to get the await statement. The Code:
uploadImage() async {
for (int i = 0; i < imageList.length; i++) {
_imageFile = imageList[i];
String fileName = Path.basename(_imageFile!.path);
Reference reference =
FirebaseStorage.instance.ref().child('uploads/$fileName');
firebase_storage.SettableMetadata(
contentType: 'image/jpeg',
customMetadata: {'picked-file-path': fileName});
UploadTask uploadTask = reference.putFile(_imageFile!);
uploadTask.whenComplete(() async {
try {
imageUrl = await reference.getDownloadURL();
print('imageUrl' + imageUrl);
finalImageList.add(imageUrl);
} catch (onError) {
print("Upload Error");
}
});
await Future.value(uploadTask)
.then((value) => {print('Upload file path ${value.ref.fullPath}')})
.onError((error, stackTrace) =>
{print('Upload file path error ${error.toString()}')});
}
}
But the method is not fast enough to store the imageUrl's in finalImageList, so the Images is online but its not connected to the object in firebase. Is there a possibility to upload it immediately or the save the imageUrl correctly? Or is my code just in the wrong order?
The FlutterFire UploadTask class extends Future, which means that you can use await on it to wait until the upload is done.
That means you can write your code much simpler as:
await reference.putFile(_imageFile!);
imageUrl = await reference.getDownloadURL();
finalImageList.add(imageUrl);
print('Upload file path ${value.ref.fullPath}')
With this change, your uploadImage will only complete after the download URL was added to finalImageList.

(Flutter Web) Firebase Storage not working with firebase plugin

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()),
);
},
)

Flutter Web Upload to Firestore

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.

Resources