3

I have the following code where I fetch an image from firebase storage as an Image. Now, I want to store this image in my CachedNetworkImage so that I don't have to fetch it every time from the DB. Since the cachednetworkimage expects a URL and I am fetching an Image, how do I use the cachednetworkimage?

Here's my code;


  final FirebaseStorage storage = FirebaseStorage(
      app: Firestore.instance.app,
      storageBucket: 'gs://my-project.appspot.com');

  Uint8List imageBytes;
  String errorMsg;

  _MyHomePageState() {
      storage.ref().child('selfies/me2.jpg').getData(10000000).then((data) =>
                setState(() {
                  imageBytes = data;
                })
        ).catchError((e) =>
                setState(() {
                  errorMsg = e.error;
                })
        );
  }

  @override
  Widget build(BuildContext context) {
    var img = imageBytes != null ? Image.memory(
        imageBytes,
        fit: BoxFit.cover,
      ) : Text(errorMsg != null ? errorMsg : "Loading...");

    return new Scaffold(
        appBar: new AppBar(
          title: new Text(widget.title),
        ),
        body: new ListView(
          children: <Widget>[
            img,
          ],
        ));
  }
}```
mcfred
  • 1,183
  • 2
  • 29
  • 68

7 Answers7

8

Cached network image and Flutter cache manager

The package Cached network image depends on another package called Flutter cache manager in order to store and retrieve image files.

Flutter cache manager

You need to download your image files and put them in the cache using the package. Here is an example code that gets files and their download urls from Firebase Storage and put them in the cache:

// import the flutter_cache_manager package
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
// ... other imports

class MyCacheManager {
  Future<void> cacheImage() async {
    final FirebaseStorage storage = FirebaseStorage(
      app: Firestore.instance.app,
      storageBucket: 'gs://my-project.appspot.com',
    );

    final Reference ref = storage.ref().child('selfies/me2.jpg');
    
    // Get your image url
    final imageUrl = await ref.getDownloadURL();

    // Download your image data
    final imageBytes = await ref.getData(10000000);

    // Put the image file in the cache
    await DefaultCacheManager().putFile(
      imageUrl,
      imageBytes,
      fileExtension: "jpg",
    );
  }
}

Cached network image

Next, you will use CacheNetworkImage widget as it shown in the documentation.

// ... some code

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: CachedNetworkImage(
      imageUrl: "your_image_link_here",
      placeholder: (context, url) => CircularProgressIndicator(),
      errorWidget: (context, url, error) => Icon(Icons.error),
    ),
  );
}

If you put your image files in the cache by using Flutter cache manager, Cached network image should retrieve them from the cache directly. If your image files expire or the cache is cleared somehow, it will download and put them in the cache for you.

Full Example

import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';

class MyCacheManager {
  final _storage = FirebaseStorage(
    app: FirebaseFirestore.instance.app,
    storageBucket: 'gs://my-project.appspot.com',
  );

  final defaultCacheManager = DefaultCacheManager();

  Future<String> cacheImage(String imagePath) async {
    final Reference ref = _storage.ref().child(imagePath);

    // Get your image url
    final imageUrl = await ref.getDownloadURL();

    // Check if the image file is not in the cache
    if ((await defaultCacheManager.getFileFromCache(imageUrl))?.file == null) {
      // Download your image data
      final imageBytes = await ref.getData(10000000);

      // Put the image file in the cache
      await defaultCacheManager.putFile(
        imageUrl,
        imageBytes,
        fileExtension: "jpg",
      );
    }

    // Return image download url
    return imageUrl;
  }
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _imageUrl;

  @override
  void initState() {
    final myCacheManager = MyCacheManager();

    // Image path from Firebase Storage
    var imagePath = 'selfies/me2.jpg';
    
    // This will try to find image in the cache first
    // If it can't find anything, it will download it from Firabase storage
    myCacheManager.cacheImage(imagePath).then((String imageUrl) {
      setState(() {
        // Get image url
        _imageUrl = imageUrl;
      });
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _imageUrl != null
            ? CachedNetworkImage(
                imageUrl: _imageUrl,
                placeholder: (context, url) => CircularProgressIndicator(),
                errorWidget: (context, url, error) => Icon(Icons.error),
              )
            : CircularProgressIndicator(),
      ),
    );
  }
}
Stewie Griffin
  • 4,690
  • 23
  • 42
  • So if I understand this solution, looking at the cache manager code you shared, it means that every time I navigate to this page, the image will be downloaded from firebase storage. How do you prevent that? How do I get to display the image to the user immediately if it's already in the cache without any showing any progress indicator? – mcfred Mar 10 '21 at 07:04
  • No, you need to call cacheImage method only once. Then, CachedNetworkImage will always use the copy of your image file from the cache unless it expires or the cache data is cleared. The key point here is to use the same image url for both caching and displaying the image. – Stewie Griffin Mar 10 '21 at 09:07
  • How do you call the cacheimage method only once? I guess that will help me alot. Because both initState() and didChangeDependencies() methods are called everytime I navigate to the screen. – mcfred Mar 10 '21 at 09:56
  • Please see the full example that I just added to my answer. Yes, the cacheImage method will be called every time you navigate to the screen, but it will download the image only if the image is not in the cache. However, you can always come up with a better logic such as caching images somewhere outside of the screen. – Stewie Griffin Mar 10 '21 at 10:43
  • So it seems like each time in the cachImage function, you are first getting the downloadURL and then checking to see if the url exists in the cache. Isn't that defeating the purpose of having the cache to begin with? – mcfred Mar 10 '21 at 15:21
  • The url part is something you need to handle while designing your app flow. The cache manager doesn't care about the url. You can provide any kind of string to put the image file in the cache, but you particularly stated that you want to use CachedNetworkImage which requires a valid url to retrieve images from cache or network. If you don't want to download urls, you will need to forget about CachedNetworkImage and use Image.file() along with flutter cache manager. This is the answer of another question. – Stewie Griffin Mar 10 '21 at 16:03
  • This the prefect answer for me, and I think for everyone is looking to cash images then show it later on! thanks – Baraa Aljabban Aug 06 '21 at 06:14
  • 1
    I think this answer needs some clarification. If OP has a valid URL, e.g.: `ref.getDownloadURL()` they can use `CachedNetworkImage` directly. If OP operates on blobs, then they need to skip `CachedNetworkImage` and as per answer cache/retrieve blob using: `DefaultCacheManager` and use e.g. `Image` widget. Answer example provides some cache warm up mechanism if OP wants to build in caches as a side effect of Firebase image load, useful in some cases. Note: I wouldn't rely on the URLs though, which might contain some dynamic params, but use `cacheKey` param e.g. `selfies/me2.jpg`. – Tom Raganowicz Jan 28 '23 at 21:27
  • @NeverEndingQueue should propose this as a complete answer, with code, because in most instances it is the correct answer. – Sam Apr 08 '23 at 09:37
  • Since the question was explicitly based on `CachedNetworkImage`, I didn't mention the `Image` widget, which wouldn't be a satisfying answer to this question anyway. Again, considering the fact that `CachedNetworkImage` was the key point, URLs were necessary to fetch the image from Firebase. Otherwise, `CachedNetworkImage` would not work properly in a case where `FirebaseStorage` was intended to be skipped and `CachedNetworkImage` was meant to be used stand alone. – Stewie Griffin Apr 09 '23 at 04:21
1

Try this way out using firebase_image package. From here . You need to sync with image url (selfies/me2.jpg) and bucket url (gs://my-project.appspot.com)

Image(
        image: FirebaseImage('gs://bucket123/userIcon123.jpg'),
        // Works with standard parameters, e.g.
        fit: BoxFit.fitWidth,
        width: 100,
        // ... etc.
      )
Shriya Pandya
  • 404
  • 3
  • 12
  • I have tried this and I was very excited when I saw this in their docs, "Therefore, any update to that remote object will result in the new version being downloaded." However, that's not the case. I am still seeing the initial image I downloaded. It would have been a fantastic thing to cache an image until it changes. – mcfred Mar 09 '21 at 08:35
0

I'm not Fire-store user, but this should work.

It might needs a little modification or something, please share in a comment to update my answer according to that

You can get file object as follow..

import 'dart:io';
import 'dart:typed_data';

Uint8List readyData = imageBytes;
File('my_image.jpg').writeAsBytes(bodyBytes);

and save it using image_gallery_saver, so the code should look like

Future<String> _createFileFromString() async {
  final encodedStr = "...";
  Uint8List bytes = base64.decode(imageBytes);
  String dir = (await getApplicationDocumentsDirectory()).path;
  String fullPath = '$dir/abc.png';
  print("local file full path ${fullPath}");
  File file = File(fullPath);
  await file.writeAsBytes(bytes);
  print(file.path);

  final result = await ImageGallerySaver.saveImage(bytes);
  print(result);

  return file.path;
}
Mohamed Sayed
  • 844
  • 5
  • 15
  • 1
    This solution will store the images to a user's gallery. That's unfortunately not what I am looking for. – mcfred Mar 09 '21 at 07:01
0

For your storage instance use some method like so

   Future<void> downloadURLExample() async {
      String downloadURL = await storage.ref('selfies/me2.jpg')
          .getDownloadURL();

  // Within your widgets:
  // CachedNetworkImage(imageUrl: downloadURL);
}
Olga
  • 533
  • 2
  • 10
0

to get it working with Firebase Storage with included offline functionality I changed it that way

Future<String> cacheImage(String imagePath) async {
    
   var fileinfo = await defaultCacheManager.getFileFromCache(imagePath);
   if(fileinfo != null)
   {
     return fileinfo.file.path;
   } else{

      final Reference ref = _storage.child(imagePath);

      // Get your image url
      final imageUrl = await ref.getDownloadURL();

      // Check if the image file is not in the cache
      
        // Download your image data
        final imageBytes = await ref.getData(10000000);

        // Put the image file in the cache
      var file = await defaultCacheManager.putFile(
          imageUrl,
          imageBytes!,
          key: imagePath,);
        

        return file.path;
   }

    }
soth
  • 1
0

for anyone still stuck with this.. try this required no hacks and uses CachedNetworkImageProvider built-in retrieval methods.

first screen:

 CachedNetworkImage(
imageUrl: "https://whereismyimage.com",
 progressIndicatorBuilder:
(context, url, progress) {
 return CircularProgressIndicator(
value: progress.progress,
 );
},
errorWidget: (context, url, error) => const Icon(Icons.error),
),

then second screen

 Image(image: CachedNetworkImageProvider("https://whereismyimage.com)"),

The CachedNetworkImageProvider knows how to retrieve the cached image using the url.

-1

Check out cached_network_image: ^2.5.0 package.

How to use it?

CachedNetworkImage(
        imageUrl: "http://via.placeholder.com/350x150",
        placeholder: (context, url) => CircularProgressIndicator(),
        errorWidget: (context, url, error) => Icon(Icons.error),
     ),
Zohaib Tariq
  • 548
  • 8
  • 15