Flutter:如何使用存储在 ApplicationDocumentsDirectory 中的照片构建照片库?

发布于 2025-01-12 04:58:52 字数 4800 浏览 1 评论 0原文

大家好,当我在 stackoverflow 和互联网上发现关于这个主题的信息不多时,我感到非常惊讶。

我尝试建立一个与此类似的照片库: 转到图库 Gif(来源 https://www.youtube.com/watch?v=yVS56cLJDgU)

用户可以拍照,我只想显示图库中用我的应用程序拍摄的照片。

这就是我所做的:

这是将存储到对象框数据库的实体。

PhotoEntity.dart

@Entity()
class PhotoEntity {
  int id;
  String description;
  String photoPath;
  @Property(type: PropertyType.date)
  @Index()
  DateTime date;

  PhotoEntity(
      {this.id = 0,
      this.description = 'no description',
      required this.photoPath,
      required this.date});

  ImageProvider getImageProvider() {
    return FileImage(File(photoPath));
  }
}

这是一个与 objectbox-databse 通信的辅助类

DatabaseController.dart

/*
 * Singleton for Database
 *
 * NOTE: Before using the DatabaseController wait for its initialization with:
 * await DatabaseController().initializationDone;
 */

class DatabaseController {
  // create Singleton instance DatabaseController
  static final DatabaseController _controller = DatabaseController._internal();

  // variable for waiting on initialization
  late Future _initialized;

  Future get initializationDone => _initialized;

  Store? _store;

  late Box<PhotoEntity> boxPhotoEntity;

  Query<PhotoEntity>? _allPhotoEntitiesQuery;
  Stream<Query<PhotoEntity>>? _allPhotoEntitiesQueryWatched;


  DatabaseController._internal() {
    _initialized = create();
  }

  factory DatabaseController() {
    return _controller;
  }

  Future<void> create() async {
    _store = await openStore();

    boxPhotoEntity = _store!.box<PhotoEntity>();
    _allPhotoEntitiesQuery = boxPhotoEntity.query().build();
    _allPhotoEntitiesQueryWatched = boxPhotoEntity.query().watch().asBroadcastStream();
  }

  void close() {
    _store?.close();
    _store = null;
  }

  void addOrUpdatePhotoEntity(PhotoEntity photoEntity) {
    _storeNullCheck();
    boxPhotoEntity .put(photoEntity);
  }

  List<PhotoEntity> getAllPhotoEntities() {
    _storeNullCheck();
    return _allPhotoEntitiesQuery!.find();
  }

  Stream<Query<PhotoEntity>> watchPhotoEntities() {
    _storeNullCheck();
    return _allPhotoEntitiesQueryWatched!;
  }

  Query<PhotoEntity> getAllPhotoEntitiesQuery() {
    _storeNullCheck();
    return _allPhotoEntitiesQuery!;
  }

  void _storeNullCheck() {
    if (_store == null) {
      if (kDebugMode) {
        print(
            'ERROR - you should call DatabaseController.create() before using it OR you shouldnt use the controller after closing it!');
      }
    }
  }
}

一旦用户拍摄了图像,它就会存储到 ApplicationDocumentsDirectory 和带有以下内容的 PhotoEntity:相应的路径存储在对象箱数据库中。

为了显示图库,我使用 StreamBuilder 以便在数据库发生更改后收到通知并相应地更新图库。

late Stream<Query<PhotoEntity>> queryStream;

@override
void initState() {
  queryStream = DatabaseController().watchPhotoEntities();
  super.initState();
}

@override
Widget build(BuildContext context) {
  return StreamBuilder(
      initialData: DatabaseController().getAllPhotoEntitiesQuery(),
      stream: queryStream,
      builder: (context, AsyncSnapshot<Query<PhotoEntity>> snapshot) {
        if (snapshot.hasError) {
          return Center(child: Text('Error ${snapshot.error}'));
        }

        if (!snapshot.hasData) {
          return const Center(child: Text('No data'));
        }

        return const GalleryGridView();
      },
  );
}

GalleryGridView 看起来像这样(这就是问题所在):

@override
Widget build(BuildContext context) {
  List<PhotoEntity> photoEntities = DatabaseController().getAllPhotoEntities();

  return GridView.builder(
    itemBuilder: (BuildContext context, int index) {
      return Image(image: photoEntities[index].getImageProvider());
    },
    gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: 150,
        childAspectRatio: 3 / 2,
        crossAxisSpacing: 20,
        mainAxisSpacing: 20),
  );
}

DatabaseController().getAllPhotoEntities() 返回数据库中的每个 PhotoEntity 显然需要太多的内存和时间来返回。

那么我实现所需行为的可能性有哪些? 我对此有一些想法:

  • 使用 objectbox,我可以将所有 PhotoEntities 作为流获取。但是如何将流转换为 GridView 呢?
  • objectbox 提供在调用 find() 之前设置 偏移量和限制 。我将如何实现一个利用它来实现分页的小部件?

如果有人能花时间给我一些建议,那就太好了。我感觉我错过了一些非常明显的东西,因为我认为这是一个非常常见的用例。

Hi i found it quite surprising when I found not much information about this topic on stackoverflow and the internet.

I try to build a Photo Gallery similar to this:
Go Gallery Gif (source https://www.youtube.com/watch?v=yVS56cLJDgU)

The user can take pictures and I only want to show the pictures in the gallery which were taken with my App.

So here is what I did:

This is the Entity which will be stored to the objectbox-database.

PhotoEntity.dart

@Entity()
class PhotoEntity {
  int id;
  String description;
  String photoPath;
  @Property(type: PropertyType.date)
  @Index()
  DateTime date;

  PhotoEntity(
      {this.id = 0,
      this.description = 'no description',
      required this.photoPath,
      required this.date});

  ImageProvider getImageProvider() {
    return FileImage(File(photoPath));
  }
}

Here is a helper class to communicate with the objectbox-databse

DatabaseController.dart

/*
 * Singleton for Database
 *
 * NOTE: Before using the DatabaseController wait for its initialization with:
 * await DatabaseController().initializationDone;
 */

class DatabaseController {
  // create Singleton instance DatabaseController
  static final DatabaseController _controller = DatabaseController._internal();

  // variable for waiting on initialization
  late Future _initialized;

  Future get initializationDone => _initialized;

  Store? _store;

  late Box<PhotoEntity> boxPhotoEntity;

  Query<PhotoEntity>? _allPhotoEntitiesQuery;
  Stream<Query<PhotoEntity>>? _allPhotoEntitiesQueryWatched;


  DatabaseController._internal() {
    _initialized = create();
  }

  factory DatabaseController() {
    return _controller;
  }

  Future<void> create() async {
    _store = await openStore();

    boxPhotoEntity = _store!.box<PhotoEntity>();
    _allPhotoEntitiesQuery = boxPhotoEntity.query().build();
    _allPhotoEntitiesQueryWatched = boxPhotoEntity.query().watch().asBroadcastStream();
  }

  void close() {
    _store?.close();
    _store = null;
  }

  void addOrUpdatePhotoEntity(PhotoEntity photoEntity) {
    _storeNullCheck();
    boxPhotoEntity .put(photoEntity);
  }

  List<PhotoEntity> getAllPhotoEntities() {
    _storeNullCheck();
    return _allPhotoEntitiesQuery!.find();
  }

  Stream<Query<PhotoEntity>> watchPhotoEntities() {
    _storeNullCheck();
    return _allPhotoEntitiesQueryWatched!;
  }

  Query<PhotoEntity> getAllPhotoEntitiesQuery() {
    _storeNullCheck();
    return _allPhotoEntitiesQuery!;
  }

  void _storeNullCheck() {
    if (_store == null) {
      if (kDebugMode) {
        print(
            'ERROR - you should call DatabaseController.create() before using it OR you shouldnt use the controller after closing it!');
      }
    }
  }
}

Once the user has taken an Image it gets stored to the ApplicationDocumentsDirectory and a PhotoEntity with the correspoing path gets stored in the objectbox-database.

For displaying the Gallery I use a StreamBuilder in order to get notified and update the Gallery accordingly once changes were made to the database.

late Stream<Query<PhotoEntity>> queryStream;

@override
void initState() {
  queryStream = DatabaseController().watchPhotoEntities();
  super.initState();
}

@override
Widget build(BuildContext context) {
  return StreamBuilder(
      initialData: DatabaseController().getAllPhotoEntitiesQuery(),
      stream: queryStream,
      builder: (context, AsyncSnapshot<Query<PhotoEntity>> snapshot) {
        if (snapshot.hasError) {
          return Center(child: Text('Error ${snapshot.error}'));
        }

        if (!snapshot.hasData) {
          return const Center(child: Text('No data'));
        }

        return const GalleryGridView();
      },
  );
}

The GalleryGridView looks like this (and here is the problem):

@override
Widget build(BuildContext context) {
  List<PhotoEntity> photoEntities = DatabaseController().getAllPhotoEntities();

  return GridView.builder(
    itemBuilder: (BuildContext context, int index) {
      return Image(image: photoEntities[index].getImageProvider());
    },
    gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: 150,
        childAspectRatio: 3 / 2,
        crossAxisSpacing: 20,
        mainAxisSpacing: 20),
  );
}

DatabaseController().getAllPhotoEntities() which returns every single PhotoEntity in the database takes obviously way too much memory and time to return.

So what are my possibilities to implement the desired behaviour?
Some thoughts I had about this:

  • With objectbox I can get all the PhotoEntities as a stream. But how do I turn a stream into a GridView?
  • objectbox offers to set an offset and a limit before calling find(). How would I implement a Widget that makes use of that in order to achieve pagination?

It would be very kind if someone would take the time and give me some advise. I have the feeling that I'm missing something very obvious because I would assume this is a very common usecase.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

把昨日还给我 2025-01-19 04:58:52

您是否有关于将流变成 Flutter 中的小部件的问题?在这种情况下,您需要使用 StreamBuilder!

Is your question about turning a stream into a widget in Flutter? In that case you need to use StreamBuilder!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文