Flutter: How to build a Photo Gallery with Photos stored in the ApplicationDocumentsDirectory?

Viewed 824

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.

1 Answers

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

Related