Flutter:如何使用存储在 ApplicationDocumentsDirectory 中的照片构建照片库?
大家好,当我在 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您是否有关于将流变成 Flutter 中的小部件的问题?在这种情况下,您需要使用 StreamBuilder!
Is your question about turning a stream into a widget in Flutter? In that case you need to use
StreamBuilder
!