Skip to content

Commit

Permalink
feat(mobile): Cache assets and albums for faster loading speed
Browse files Browse the repository at this point in the history
feat(mobile): Cache assets and albums for faster loading speed
  • Loading branch information
alextran1502 authored Oct 19, 2022
2 parents d6d525c + 3617433 commit 061b229
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 7 deletions.
22 changes: 20 additions & 2 deletions mobile/lib/modules/album/providers/album.provider.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/album/services/album_cache.service.dart';
import 'package:openapi/api.dart';

class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
AlbumNotifier(this._albumService) : super([]);
AlbumNotifier(this._albumService, this._albumCacheService) : super([]);
final AlbumService _albumService;
final AlbumCacheService _albumCacheService;

_cacheState() {
_albumCacheService.put(state);
}

getAllAlbums() async {

if (await _albumCacheService.isValid() && state.isEmpty) {
state = await _albumCacheService.get();
}

List<AlbumResponseDto>? albums =
await _albumService.getAlbums(isShared: false);

if (albums != null) {
state = albums;
_cacheState();
}
}

deleteAlbum(String albumId) {
state = state.where((album) => album.id != albumId).toList();
_cacheState();
}

Future<AlbumResponseDto?> createAlbum(
Expand All @@ -28,6 +41,8 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {

if (album != null) {
state = [...state, album];
_cacheState();

return album;
}
return null;
Expand All @@ -36,5 +51,8 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {

final albumProvider =
StateNotifierProvider<AlbumNotifier, List<AlbumResponseDto>>((ref) {
return AlbumNotifier(ref.watch(albumServiceProvider));
return AlbumNotifier(
ref.watch(albumServiceProvider),
ref.watch(albumCacheServiceProvider),
);
});
21 changes: 19 additions & 2 deletions mobile/lib/modules/album/providers/shared_album.provider.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/album/services/album_cache.service.dart';
import 'package:openapi/api.dart';

class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
SharedAlbumNotifier(this._sharedAlbumService) : super([]);
SharedAlbumNotifier(this._sharedAlbumService, this._sharedAlbumCacheService) : super([]);

final AlbumService _sharedAlbumService;
final SharedAlbumCacheService _sharedAlbumCacheService;

_cacheState() {
_sharedAlbumCacheService.put(state);
}

Future<AlbumResponseDto?> createSharedAlbum(
String albumName,
Expand All @@ -22,6 +28,7 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {

if (newAlbum != null) {
state = [...state, newAlbum];
_cacheState();
}

return newAlbum;
Expand All @@ -33,23 +40,30 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
}

getAllSharedAlbums() async {
if (await _sharedAlbumCacheService.isValid() && state.isEmpty) {
state = await _sharedAlbumCacheService.get();
}

List<AlbumResponseDto>? sharedAlbums =
await _sharedAlbumService.getAlbums(isShared: true);

if (sharedAlbums != null) {
state = sharedAlbums;
_cacheState();
}
}

deleteAlbum(String albumId) async {
state = state.where((album) => album.id != albumId).toList();
_cacheState();
}

Future<bool> leaveAlbum(String albumId) async {
var res = await _sharedAlbumService.leaveAlbum(albumId);

if (res) {
state = state.where((album) => album.id != albumId).toList();
_cacheState();
return true;
} else {
return false;
Expand All @@ -72,7 +86,10 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {

final sharedAlbumProvider =
StateNotifierProvider<SharedAlbumNotifier, List<AlbumResponseDto>>((ref) {
return SharedAlbumNotifier(ref.watch(albumServiceProvider));
return SharedAlbumNotifier(
ref.watch(albumServiceProvider),
ref.watch(sharedAlbumCacheServiceProvider),
);
});

final sharedAlbumDetailProvider = FutureProvider.autoDispose
Expand Down
49 changes: 49 additions & 0 deletions mobile/lib/modules/album/services/album_cache.service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/services/json_cache.dart';
import 'package:openapi/api.dart';

class BaseAlbumCacheService extends JsonCache<List<AlbumResponseDto>> {
BaseAlbumCacheService(super.cacheFileName);

@override
void put(List<AlbumResponseDto> data) {
putRawData(data.map((e) => e.toJson()).toList());
}

@override
Future<List<AlbumResponseDto>> get() async {
try {
final mapList = await readRawData() as List<dynamic>;

final responseData = mapList
.map((e) => AlbumResponseDto.fromJson(e))
.whereNotNull()
.toList();

return responseData;
} catch (e) {
debugPrint(e.toString());
return [];
}
}
}

class AlbumCacheService extends BaseAlbumCacheService {
AlbumCacheService() : super("album_cache");
}

class SharedAlbumCacheService extends BaseAlbumCacheService {
SharedAlbumCacheService() : super("shared_album_cache");
}

final albumCacheServiceProvider = Provider(
(ref) => AlbumCacheService(),
);

final sharedAlbumCacheServiceProvider = Provider(
(ref) => SharedAlbumCacheService(),
);

37 changes: 37 additions & 0 deletions mobile/lib/modules/home/services/asset_cache.service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/services/json_cache.dart';
import 'package:openapi/api.dart';


class AssetCacheService extends JsonCache<List<AssetResponseDto>> {
AssetCacheService() : super("asset_cache");

@override
void put(List<AssetResponseDto> data) {
putRawData(data.map((e) => e.toJson()).toList());
}

@override
Future<List<AssetResponseDto>> get() async {
try {
final mapList = await readRawData() as List<dynamic>;

final responseData = mapList
.map((e) => AssetResponseDto.fromJson(e))
.whereNotNull()
.toList();

return responseData;
} catch (e) {
debugPrint(e.toString());

return [];
}
}
}

final assetCacheServiceProvider = Provider(
(ref) => AssetCacheService(),
);
15 changes: 14 additions & 1 deletion mobile/lib/modules/login/providers/authentication.provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/album/services/album_cache.service.dart';
import 'package:immich_mobile/modules/home/services/asset_cache.service.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
import 'package:immich_mobile/modules/backup/services/backup.service.dart';
Expand All @@ -16,6 +18,9 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
this._deviceInfoService,
this._backupService,
this._apiService,
this._assetCacheService,
this._albumCacheService,
this._sharedAlbumCacheService,
) : super(
AuthenticationState(
deviceId: "",
Expand All @@ -42,6 +47,9 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
final DeviceInfoService _deviceInfoService;
final BackupService _backupService;
final ApiService _apiService;
final AssetCacheService _assetCacheService;
final AlbumCacheService _albumCacheService;
final SharedAlbumCacheService _sharedAlbumCacheService;

Future<bool> login(
String email,
Expand Down Expand Up @@ -153,7 +161,9 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
Future<bool> logout() async {
Hive.box(userInfoBox).delete(accessTokenKey);
state = state.copyWith(isAuthenticated: false);

_assetCacheService.invalidate();
_albumCacheService.invalidate();
_sharedAlbumCacheService.invalidate();
return true;
}

Expand Down Expand Up @@ -199,5 +209,8 @@ final authenticationProvider =
ref.watch(deviceInfoServiceProvider),
ref.watch(backupServiceProvider),
ref.watch(apiServiceProvider),
ref.watch(assetCacheServiceProvider),
ref.watch(albumCacheServiceProvider),
ref.watch(sharedAlbumCacheServiceProvider),
);
});
34 changes: 32 additions & 2 deletions mobile/lib/shared/providers/asset.provider.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/services/asset.service.dart';
import 'package:immich_mobile/modules/home/services/asset_cache.service.dart';
import 'package:immich_mobile/shared/services/device_info.service.dart';
import 'package:collection/collection.dart';
import 'package:intl/intl.dart';
Expand All @@ -9,24 +10,50 @@ import 'package:photo_manager/photo_manager.dart';

class AssetNotifier extends StateNotifier<List<AssetResponseDto>> {
final AssetService _assetService;
final AssetCacheService _assetCacheService;

final DeviceInfoService _deviceInfoService = DeviceInfoService();

AssetNotifier(this._assetService) : super([]);
AssetNotifier(this._assetService, this._assetCacheService) : super([]);

_cacheState() {
_assetCacheService.put(state);
}

getAllAsset() async {
final stopwatch = Stopwatch();


if (await _assetCacheService.isValid() && state.isEmpty) {
stopwatch.start();
state = await _assetCacheService.get();
debugPrint("Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms");
stopwatch.reset();
}

stopwatch.start();
var allAssets = await _assetService.getAllAsset();
debugPrint("Query assets from API: ${stopwatch.elapsedMilliseconds}ms");
stopwatch.reset();

if (allAssets != null) {
state = allAssets;

stopwatch.start();
_cacheState();
debugPrint("Store assets in cache: ${stopwatch.elapsedMilliseconds}ms");
stopwatch.reset();
}
}

clearAllAsset() {
state = [];
_cacheState();
}

onNewAssetUploaded(AssetResponseDto newAsset) {
state = [...state, newAsset];
_cacheState();
}

deleteAssets(Set<AssetResponseDto> deleteAssets) async {
Expand Down Expand Up @@ -65,12 +92,15 @@ class AssetNotifier extends StateNotifier<List<AssetResponseDto>> {
state.where((immichAsset) => immichAsset.id != asset.id).toList();
}
}

_cacheState();
}
}

final assetProvider =
StateNotifierProvider<AssetNotifier, List<AssetResponseDto>>((ref) {
return AssetNotifier(ref.watch(assetServiceProvider));
return AssetNotifier(
ref.watch(assetServiceProvider), ref.watch(assetCacheServiceProvider));
});

final assetGroupByDateTimeProvider = StateProvider((ref) {
Expand Down
49 changes: 49 additions & 0 deletions mobile/lib/shared/services/json_cache.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'dart:convert';
import 'dart:io';

import 'package:path_provider/path_provider.dart';

abstract class JsonCache<T> {
final String cacheFileName;

JsonCache(this.cacheFileName);

Future<File> _getCacheFile() async {
final basePath = await getTemporaryDirectory();
final basePathName = basePath.path;

final file = File("$basePathName/$cacheFileName.bin");

return file;
}

Future<bool> isValid() async {
final file = await _getCacheFile();
return await file.exists();
}

Future<void> invalidate() async {
final file = await _getCacheFile();
await file.delete();
}

Future<void> putRawData(dynamic data) async {
final jsonString = json.encode(data);
final file = await _getCacheFile();

if (!await file.exists()) {
await file.create();
}

await file.writeAsString(jsonString);
}

dynamic readRawData() async {
final file = await _getCacheFile();
final data = await file.readAsString();
return json.decode(data);
}

void put(T data);
Future<T> get();
}

0 comments on commit 061b229

Please sign in to comment.