如何在Flutter中将图像存储到cached_network_image?

3
我有以下代码,从Firebase存储中获取图像作为Image。现在,我想将此图像存储在我的CachedNetworkImage中,以便我不必每次从DB中获取它。由于cachednetworkimage需要一个URL而我正在获取一个Image,我该如何使用cachednetworkimage?
这是我的代码:

  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,
          ],
        ));
  }
}```
7个回答

8

缓存网络图像和Flutter缓存管理器

包Cached network image依赖于另一个名为Flutter cache manager的包,以便存储和检索图像文件。

Flutter缓存管理器

您需要使用该包下载图像文件并将其放入缓存中。以下是一个示例代码,它从Firebase存储获取文件及其下载URL,并将它们放入缓存中:

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

缓存网络图片

接下来,您将按照文档中的示例使用 CachedNetworkImage 组件。

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

如果你使用Flutter缓存管理器将图像文件放入缓存中,Cached network image应该直接从缓存中检索它们。如果您的图像文件过期或缓存被清除,它会为您下载并将它们放入缓存。

完整示例

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

所以如果我理解这个解决方案,根据你分享的缓存管理器代码,这意味着每次我导航到这个页面时,图片都会从Firebase存储中下载。你是如何防止这种情况发生的?如果图片已经在缓存中,我如何立即将其显示给用户,而不显示任何进度指示器? - mcfred
你如何只调用一次cacheimage方法?我猜这会对我很有帮助。因为每次导航到屏幕时都会调用initState()和didChangeDependencies()方法。 - mcfred
在设计应用程序流程时,URL 部分是您需要处理的内容。缓存管理器不关心 URL。您可以提供任何类型的字符串来将图像文件放入缓存中,但您特别指出要使用 CachedNetworkImage,该图像需要有效的 URL 从缓存或网络中检索图像。如果您不想下载 URL,则需要忘记 CachedNetworkImage,并使用 Image.file() 以及 Flutter 缓存管理器。这是另一个问题的答案。 - Stewie Griffin
这对我来说是完美的答案,我认为对于所有想要提取图像并稍后显示它的人来说也是如此!谢谢 - Baraa Aljabban
1
我认为这个答案需要一些澄清。 如果 OP 有一个有效的 URL,例如:ref.getDownloadURL(),他们可以直接使用 CachedNetworkImage。 如果 OP 操作的是 blob,则需要跳过 CachedNetworkImage,并按照答案中所述使用 DefaultCacheManager 缓存/检索 blob,并使用例如 Image 小部件。 答案示例提供了一些缓存预热机制,如果 OP 希望在 Firebase 图像加载的副作用中构建缓存,则在某些情况下非常有用。 注意:我不会依赖 URL,因为它们可能包含一些动态参数,而是使用 cacheKey 参数,例如 selfies/me2.jpg - NeverEndingQueue
显示剩余5条评论

1
使用firebase_image包尝试这种方式。从这里开始。您需要与图像URL(selfies / me2.jpg)和存储桶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.
      )

我尝试过这个功能,当我在文档中看到这句话时,感到非常兴奋:“因此,对远程对象的任何更新都将导致下载新版本。” 但是,事实并非如此。我仍然看到最初下载的图像。如果能够缓存图像直到其发生更改,那将是一件非常棒的事情。 - mcfred

0

对于仍然卡在这个问题上的人,请尝试使用内置检索方法的CachedNetworkImageProvider,无需任何黑客技巧。

第一个屏幕:

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

然后是第二个屏幕

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

CachedNetworkImageProvider 知道如何使用 URL 获取缓存的图像。


0

为了让它与包含离线功能的Firebase存储一起工作,我进行了如下更改

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;
   }

    }

0

对于您的存储实例,请使用以下方法之一

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

  // Within your widgets:
  // CachedNetworkImage(imageUrl: downloadURL);
}

0

我不是 Fire-store 用户,但这应该可以运行。

可能需要一些修改或其他操作,请在评论中分享以便根据情况更新我的答案。

您可以按如下方式获取文件对象...

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

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

并使用image_gallery_saver进行保存,因此代码应如下所示

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;
}

1
此解决方案将图像存储到用户的相册中。很遗憾,这不是我在寻找的。 - mcfred

-1

查看 cached_network_image: ^2.5.0 包。

如何使用它?

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

请仔细阅读我的问题。我得到的是一张图片,而不是一个URL。 - mcfred

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接