Skip to content

Commit dd7cd50

Browse files
authored
Refactor repositories (bizz84#116)
* Split FirestoreRepository into JobsRepository & EntriesRepository, CRUD support * Cleanup paths * Cleanup start/end difference * More cleanup * Move Entry file * Add comment
1 parent 0181368 commit dd7cd50

22 files changed

+338
-336
lines changed

ios/Podfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe
2828
flutter_ios_podfile_setup
2929

3030
target 'Runner' do
31-
pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '10.1.0'
31+
pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '10.3.0'
3232
use_frameworks!
3333
use_modular_headers!
3434

ios/Podfile.lock

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,74 @@
11
PODS:
2-
- cloud_firestore (4.0.5):
3-
- Firebase/Firestore (= 10.1.0)
2+
- cloud_firestore (4.4.3):
3+
- Firebase/Firestore (= 10.3.0)
44
- firebase_core
55
- Flutter
66
- nanopb (< 2.30910.0, >= 2.30908.0)
7-
- Firebase/Auth (10.1.0):
7+
- Firebase/Auth (10.3.0):
88
- Firebase/CoreOnly
9-
- FirebaseAuth (~> 10.1.0)
10-
- Firebase/CoreOnly (10.1.0):
11-
- FirebaseCore (= 10.1.0)
12-
- Firebase/Firestore (10.1.0):
9+
- FirebaseAuth (~> 10.3.0)
10+
- Firebase/CoreOnly (10.3.0):
11+
- FirebaseCore (= 10.3.0)
12+
- Firebase/Firestore (10.3.0):
1313
- Firebase/CoreOnly
14-
- FirebaseFirestore (~> 10.1.0)
14+
- FirebaseFirestore (~> 10.3.0)
1515
- firebase_auth (4.1.2):
16-
- Firebase/Auth (= 10.1.0)
16+
- Firebase/Auth (= 10.3.0)
1717
- firebase_core
1818
- Flutter
19-
- firebase_core (2.2.0):
20-
- Firebase/CoreOnly (= 10.1.0)
19+
- firebase_core (2.7.0):
20+
- Firebase/CoreOnly (= 10.3.0)
2121
- Flutter
22-
- FirebaseAuth (10.1.0):
22+
- FirebaseAuth (10.3.0):
2323
- FirebaseCore (~> 10.0)
2424
- GoogleUtilities/AppDelegateSwizzler (~> 7.8)
2525
- GoogleUtilities/Environment (~> 7.8)
26-
- GTMSessionFetcher/Core (~> 2.1)
27-
- FirebaseCore (10.1.0):
26+
- GTMSessionFetcher/Core (< 4.0, >= 2.1)
27+
- FirebaseCore (10.3.0):
2828
- FirebaseCoreInternal (~> 10.0)
2929
- GoogleUtilities/Environment (~> 7.8)
3030
- GoogleUtilities/Logger (~> 7.8)
31-
- FirebaseCoreInternal (10.1.0):
31+
- FirebaseCoreInternal (10.5.0):
3232
- "GoogleUtilities/NSData+zlib (~> 7.8)"
33-
- FirebaseFirestore (10.1.0):
34-
- FirebaseFirestore/AutodetectLeveldb (= 10.1.0)
35-
- FirebaseFirestore/AutodetectLeveldb (10.1.0):
33+
- FirebaseFirestore (10.3.0):
34+
- FirebaseFirestore/AutodetectLeveldb (= 10.3.0)
35+
- FirebaseFirestore/AutodetectLeveldb (10.3.0):
3636
- FirebaseFirestore/Base
3737
- FirebaseFirestore/WithLeveldb
38-
- FirebaseFirestore/Base (10.1.0)
39-
- FirebaseFirestore/WithLeveldb (10.1.0):
38+
- FirebaseFirestore/Base (10.3.0)
39+
- FirebaseFirestore/WithLeveldb (10.3.0):
4040
- FirebaseFirestore/Base
4141
- Flutter (1.0.0)
42-
- GoogleUtilities/AppDelegateSwizzler (7.10.0):
42+
- GoogleUtilities/AppDelegateSwizzler (7.11.0):
4343
- GoogleUtilities/Environment
4444
- GoogleUtilities/Logger
4545
- GoogleUtilities/Network
46-
- GoogleUtilities/Environment (7.10.0):
46+
- GoogleUtilities/Environment (7.11.0):
4747
- PromisesObjC (< 3.0, >= 1.2)
48-
- GoogleUtilities/Logger (7.10.0):
48+
- GoogleUtilities/Logger (7.11.0):
4949
- GoogleUtilities/Environment
50-
- GoogleUtilities/Network (7.10.0):
50+
- GoogleUtilities/Network (7.11.0):
5151
- GoogleUtilities/Logger
5252
- "GoogleUtilities/NSData+zlib"
5353
- GoogleUtilities/Reachability
54-
- "GoogleUtilities/NSData+zlib (7.10.0)"
55-
- GoogleUtilities/Reachability (7.10.0):
54+
- "GoogleUtilities/NSData+zlib (7.11.0)"
55+
- GoogleUtilities/Reachability (7.11.0):
5656
- GoogleUtilities/Logger
57-
- GTMSessionFetcher/Core (2.3.0)
57+
- GTMSessionFetcher/Core (3.1.0)
5858
- nanopb (2.30909.0):
5959
- nanopb/decode (= 2.30909.0)
6060
- nanopb/encode (= 2.30909.0)
6161
- nanopb/decode (2.30909.0)
6262
- nanopb/encode (2.30909.0)
63-
- PromisesObjC (2.1.1)
63+
- PromisesObjC (2.2.0)
6464
- shared_preferences_ios (0.0.1):
6565
- Flutter
6666

6767
DEPENDENCIES:
6868
- cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`)
6969
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
7070
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
71-
- FirebaseFirestore (from `https://github.com/invertase/firestore-ios-sdk-frameworks.git`, tag `10.1.0`)
71+
- FirebaseFirestore (from `https://github.com/invertase/firestore-ios-sdk-frameworks.git`, tag `10.3.0`)
7272
- Flutter (from `Flutter`)
7373
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
7474

@@ -92,7 +92,7 @@ EXTERNAL SOURCES:
9292
:path: ".symlinks/plugins/firebase_core/ios"
9393
FirebaseFirestore:
9494
:git: https://github.com/invertase/firestore-ios-sdk-frameworks.git
95-
:tag: 10.1.0
95+
:tag: 10.3.0
9696
Flutter:
9797
:path: Flutter
9898
shared_preferences_ios:
@@ -101,24 +101,24 @@ EXTERNAL SOURCES:
101101
CHECKOUT OPTIONS:
102102
FirebaseFirestore:
103103
:git: https://github.com/invertase/firestore-ios-sdk-frameworks.git
104-
:tag: 10.1.0
104+
:tag: 10.3.0
105105

106106
SPEC CHECKSUMS:
107-
cloud_firestore: 345ab5f423db6ae492abb648372156bdccb9df42
108-
Firebase: 444b35a9c568a516666213c2f6cccd10cb12559f
109-
firebase_auth: 6e24814d3c9976a288da6bb7a49ac79616863b5b
110-
firebase_core: d2242c6f318db1d0dcecfbfa491e943337b0d755
111-
FirebaseAuth: 19a85b8a42e7c1104a2ffa6987c748daa79a5e64
112-
FirebaseCore: 55e7ae35991ccca4db03ff8d8df6ed5f17a3e4c7
113-
FirebaseCoreInternal: 96d75228e10fd369564da51bd898414eb0f54df5
114-
FirebaseFirestore: c55b29bb38afeed2fe73c241e55c7f8230ba7abc
107+
cloud_firestore: 94326bb743acbfe43c68f933dab8a094e84d5849
108+
Firebase: f92fc551ead69c94168d36c2b26188263860acd9
109+
firebase_auth: f1580b179692c741663fa01535a66b446c128638
110+
firebase_core: 128d8c43c3a453a4a67463314fc3761bedff860b
111+
FirebaseAuth: 0e415d29d846c1dce2fb641e46f35e9888d9bec6
112+
FirebaseCore: 988754646ab3bd4bdcb740f1bfe26b9f6c0d5f2a
113+
FirebaseCoreInternal: e463f41bb935cd049505bf7e9a5bdd7dcea90df6
114+
FirebaseFirestore: f16ed5e8e5b024eff4928bc33605df9526bda650
115115
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
116-
GoogleUtilities: bad72cb363809015b1f7f19beb1f1cd23c589f95
117-
GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2
116+
GoogleUtilities: c2bdc4cf2ce786c4d2e6b3bcfd599a25ca78f06f
117+
GTMSessionFetcher: c9e714f7eec91a55641e2bab9f45fd83a219b882
118118
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431
119-
PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb
119+
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
120120
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
121121

122-
PODFILE CHECKSUM: 21ac9d4574524fb5f3601d1a026bd1e110fc75b1
122+
PODFILE CHECKSUM: 8ed72f4eb3f774eb823caa5f6b48b21a5eb9975a
123123

124124
COCOAPODS: 1.11.3

lib/src/features/entries/application/entries_service.dart

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,27 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
22
import 'package:rxdart/rxdart.dart';
33
import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart';
44
import 'package:starter_architecture_flutter_firebase/src/features/authentication/domain/app_user.dart';
5+
import 'package:starter_architecture_flutter_firebase/src/features/entries/data/entries_repository.dart';
56
import 'package:starter_architecture_flutter_firebase/src/features/entries/domain/daily_jobs_details.dart';
67
import 'package:starter_architecture_flutter_firebase/src/features/entries/domain/entries_list_tile_model.dart';
78
import 'package:starter_architecture_flutter_firebase/src/features/entries/domain/entry_job.dart';
8-
import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart';
9+
import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/jobs_repository.dart';
910
import 'package:starter_architecture_flutter_firebase/src/utils/format.dart';
1011
import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart';
1112
import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart';
1213

1314
// TODO: Clean up this code a bit more
1415
class EntriesService {
15-
EntriesService({required this.database});
16-
final FirestoreRepository database;
16+
EntriesService(
17+
{required this.jobsRepository, required this.entriesRepository});
18+
final JobsRepository jobsRepository;
19+
final EntriesRepository entriesRepository;
1720

1821
/// combine List<Job>, List<Entry> into List<EntryJob>
1922
Stream<List<EntryJob>> _allEntriesStream(UserID uid) =>
2023
CombineLatestStream.combine2(
21-
database.watchEntries(uid: uid),
22-
database.watchJobs(uid: uid),
24+
entriesRepository.watchEntries(uid: uid),
25+
jobsRepository.watchJobs(uid: uid),
2326
_entriesJobsCombiner,
2427
);
2528

@@ -76,7 +79,10 @@ class EntriesService {
7679
}
7780

7881
final entriesServiceProvider = Provider<EntriesService>((ref) {
79-
return EntriesService(database: ref.watch(databaseProvider));
82+
return EntriesService(
83+
jobsRepository: ref.watch(jobsRepositoryProvider),
84+
entriesRepository: ref.watch(entriesRepositoryProvider),
85+
);
8086
});
8187

8288
final entriesTileModelStreamProvider =
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import 'package:cloud_firestore/cloud_firestore.dart';
2+
import 'package:flutter_riverpod/flutter_riverpod.dart';
3+
import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart';
4+
import 'package:starter_architecture_flutter_firebase/src/features/authentication/domain/app_user.dart';
5+
import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart';
6+
import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart';
7+
8+
class EntriesRepository {
9+
const EntriesRepository(this._firestore);
10+
final FirebaseFirestore _firestore;
11+
12+
static String entryPath(String uid, String entryId) =>
13+
'users/$uid/entries/$entryId';
14+
static String entriesPath(String uid) => 'users/$uid/entries';
15+
16+
// create
17+
Future<void> addEntry({
18+
required UserID uid,
19+
required JobID jobId,
20+
required DateTime start,
21+
required DateTime end,
22+
required String comment,
23+
}) =>
24+
_firestore.collection(entriesPath(uid)).add({
25+
'jobId': jobId,
26+
'start': start.millisecondsSinceEpoch,
27+
'end': end.millisecondsSinceEpoch,
28+
'comment': comment,
29+
});
30+
31+
// update
32+
Future<void> updateEntry({
33+
required UserID uid,
34+
required Entry entry,
35+
}) =>
36+
_firestore.doc(entryPath(uid, entry.id)).update(entry.toMap());
37+
38+
// delete
39+
Future<void> deleteEntry({required UserID uid, required EntryID entryId}) =>
40+
_firestore.doc(entryPath(uid, entryId)).delete();
41+
42+
// read
43+
Stream<List<Entry>> watchEntries({required UserID uid, JobID? jobId}) =>
44+
queryEntries(uid: uid, jobId: jobId)
45+
.snapshots()
46+
.map((snapshot) => snapshot.docs.map((doc) => doc.data()).toList());
47+
48+
Query<Entry> queryEntries({required UserID uid, JobID? jobId}) {
49+
Query<Entry> query =
50+
_firestore.collection(entriesPath(uid)).withConverter<Entry>(
51+
fromFirestore: (snapshot, _) =>
52+
Entry.fromMap(snapshot.data()!, snapshot.id),
53+
toFirestore: (entry, _) => entry.toMap(),
54+
);
55+
if (jobId != null) {
56+
query = query.where('jobId', isEqualTo: jobId);
57+
}
58+
return query;
59+
}
60+
}
61+
62+
final entriesRepositoryProvider = Provider<EntriesRepository>((ref) {
63+
return EntriesRepository(FirebaseFirestore.instance);
64+
});
65+
66+
final jobEntriesStreamProvider =
67+
StreamProvider.autoDispose.family<List<Entry>, JobID>((ref, jobId) {
68+
final user = ref.watch(authStateChangesProvider).value;
69+
if (user == null) {
70+
throw AssertionError('User can\'t be null when fetching jobs');
71+
}
72+
final repository = ref.watch(entriesRepositoryProvider);
73+
return repository.watchEntries(uid: user.uid, jobId: jobId);
74+
});

lib/src/features/jobs/domain/entry.dart renamed to lib/src/features/entries/domain/entry.dart

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:equatable/equatable.dart';
2+
import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart';
23

34
typedef EntryID = String;
45

@@ -10,9 +11,8 @@ class Entry extends Equatable {
1011
required this.end,
1112
required this.comment,
1213
});
13-
1414
final EntryID id;
15-
final String jobId;
15+
final JobID jobId;
1616
final DateTime start;
1717
final DateTime end;
1818
final String comment;
@@ -26,10 +26,7 @@ class Entry extends Equatable {
2626
double get durationInHours =>
2727
end.difference(start).inMinutes.toDouble() / 60.0;
2828

29-
factory Entry.fromMap(Map<dynamic, dynamic>? value, String id) {
30-
if (value == null) {
31-
throw StateError('missing data for entryId: $id');
32-
}
29+
factory Entry.fromMap(Map<dynamic, dynamic> value, EntryID id) {
3330
final startMilliseconds = value['start'] as int;
3431
final endMilliseconds = value['end'] as int;
3532
return Entry(

lib/src/features/jobs/presentation/entry_screen/entry_screen.dart renamed to lib/src/features/entries/presentation/entry_screen/entry_screen.dart

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import 'package:flutter/material.dart';
44
import 'package:flutter_riverpod/flutter_riverpod.dart';
55
import 'package:go_router/go_router.dart';
66
import 'package:starter_architecture_flutter_firebase/src/common_widgets/date_time_picker.dart';
7-
import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart';
87
import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart';
98
import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart';
10-
import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/entry_screen/entry_screen_controller.dart';
9+
import 'package:starter_architecture_flutter_firebase/src/features/entries/presentation/entry_screen/entry_screen_controller.dart';
1110
import 'package:starter_architecture_flutter_firebase/src/utils/async_value_ui.dart';
1211
import 'package:starter_architecture_flutter_firebase/src/utils/format.dart';
1312

@@ -28,6 +27,11 @@ class _EntryPageState extends ConsumerState<EntryScreen> {
2827
late TimeOfDay _endTime;
2928
late String _comment;
3029

30+
DateTime get start => DateTime(_startDate.year, _startDate.month,
31+
_startDate.day, _startTime.hour, _startTime.minute);
32+
DateTime get end => DateTime(_endDate.year, _endDate.month, _endDate.day,
33+
_endTime.hour, _endTime.minute);
34+
3135
@override
3236
void initState() {
3337
super.initState();
@@ -42,25 +46,15 @@ class _EntryPageState extends ConsumerState<EntryScreen> {
4246
_comment = widget.entry?.comment ?? '';
4347
}
4448

45-
Entry _entryFromState() {
46-
final start = DateTime(_startDate.year, _startDate.month, _startDate.day,
47-
_startTime.hour, _startTime.minute);
48-
final end = DateTime(_endDate.year, _endDate.month, _endDate.day,
49-
_endTime.hour, _endTime.minute);
50-
final id = widget.entry?.id ?? documentIdFromCurrentDate();
51-
return Entry(
52-
id: id,
53-
jobId: widget.jobId,
54-
start: start,
55-
end: end,
56-
comment: _comment,
57-
);
58-
}
59-
6049
Future<void> _setEntryAndDismiss() async {
61-
final entry = _entryFromState();
6250
final success =
63-
await ref.read(entryScreenControllerProvider.notifier).setEntry(entry);
51+
await ref.read(entryScreenControllerProvider.notifier).submit(
52+
entryId: widget.entryId,
53+
jobId: widget.jobId,
54+
start: start,
55+
end: end,
56+
comment: _comment,
57+
);
6458
if (success && mounted) {
6559
context.pop();
6660
}
@@ -126,8 +120,8 @@ class _EntryPageState extends ConsumerState<EntryScreen> {
126120
}
127121

128122
Widget _buildDuration() {
129-
final currentEntry = _entryFromState();
130-
final durationFormatted = Format.hours(currentEntry.durationInHours);
123+
final durationInHours = end.difference(start).inMinutes.toDouble() / 60.0;
124+
final durationFormatted = Format.hours(durationInHours);
131125
return Row(
132126
mainAxisAlignment: MainAxisAlignment.end,
133127
children: <Widget>[

0 commit comments

Comments
 (0)