Conversation
Levi-Lesches
left a comment
There was a problem hiding this comment.
First of all, you clearly understand the structure of a service and how to create one. Problem is, you don't really need to here. The root of the issue is that you're not accessing these timestamps for the sake of showing them in the app; you need them to manipulate other data (by refreshing them). Which means it shouldn't be its own service that your models call directly, but rather something that should be handled implicitly by the services layer, maybe in Database.init.
If you split your code into two parts, 1) deciding whether to update data, and 2) updating the data, you should only make part 1 implicit in Database.init. Remember that the user can always manually refresh the data in the UI, so you should make part 2 a separate function, perhaps a .update() on each HybridX.
All my comments in the code are either formatting or just an in-context explanation of the above.
lib/src/models/data/schedule.dart
Outdated
| ]; | ||
| setToday(); | ||
| notifyListeners(); | ||
| await Services.instance.database.dataRefresh.setRefreshData("schedule"); |
There was a problem hiding this comment.
Notice how you're using HybridCalendar here. That means you're not guaranteed to get data from the cloud. Which is a good thing: the on-device database is just as valid. The only place where you'd know where you got the data from would be either:
- an explicit call to
calendarDatabase.cloud.getSomething() - in the
HybridCalendaritself, where you can see which database is chosen for each function
Try to figure out a better place to put this.
lib/src/models/data/user.dart
Outdated
| for (final String scope in scopeStrings) | ||
| parseAdminScope(scope) | ||
| ]; | ||
| await Services.instance.database.dataRefresh.setRefreshData("user"); |
| try { | ||
| await Services.instance.database.calendar.signIn(); | ||
| await Services.instance.database.sports.signIn(); | ||
| await schedule.initCalendar(); |
There was a problem hiding this comment.
Do you not need to initialize the calendar?
There was a problem hiding this comment.
I call schedule.init() in .getRefreshData(). schedule.init() calls init calendar in it so it was initializing twice
| await Services.instance.database.calendar.signIn(); | ||
| await Services.instance.database.sports.signIn(); | ||
| await schedule.initCalendar(); | ||
| await Services.instance.database.dataRefresh.getRefreshData(); |
There was a problem hiding this comment.
Without even seeing what this does, you can probably tell it's not really named that well. The above functions do things, like signIn or initCalendar, but this function just gets something (the refresh data) and ignores the result. I'll leave more comments in that function, just keep this in mind. You always want to name your functions in the form of a question, like isX or hasX, or a command, like getX, setX, and initX.
| @override | ||
| Future<void> signIn() async { } | ||
| @override | ||
| final CloudDataRefresh cloud = CloudDataRefresh(); | ||
| @override | ||
| final LocalDataRefresh local = LocalDataRefresh(); | ||
| @override | ||
| Future<void> getRefreshData() => cloud.getRefreshData(); | ||
| @override | ||
| Future<void> setRefreshData(String data) => local.setRefreshData(data); |
There was a problem hiding this comment.
| @override | |
| Future<void> signIn() async { } | |
| @override | |
| final CloudDataRefresh cloud = CloudDataRefresh(); | |
| @override | |
| final LocalDataRefresh local = LocalDataRefresh(); | |
| @override | |
| Future<void> getRefreshData() => cloud.getRefreshData(); | |
| @override | |
| Future<void> setRefreshData(String data) => local.setRefreshData(data); | |
| @override | |
| Future<void> signIn() async { } | |
| @override | |
| final CloudDataRefresh cloud = CloudDataRefresh(); | |
| @override | |
| final LocalDataRefresh local = LocalDataRefresh(); | |
| @override | |
| Future<void> getRefreshData() => cloud.getRefreshData(); | |
| @override | |
| Future<void> setRefreshData(String data) => local.setRefreshData(data); |
First of all, spacing.
Secondly, notice that you separate the cloud and local databases by splitting them into getRefreshData and setRefreshData. That's good design within HybridDataRefresh, since you care about having a difference between the two. However, the function that uses the HybridDataRefresh (like Database.init or Services.init) won't care, and will just want the data to come from the right places automatically.
That's the whole point of the HybridX interface. It's to compile the cloud and local databases into a single class that knows when to use which so that every other function can call HybridX.doSomething() without worrying. All this to say that you should have a function, maybe Future<void> update(String data), that 1) downloads the refresh timestamp from the cloud, 2) compares it with the local timestamp, 3) updates the data if needed, and 4) save the current timestamp to the local database.
You'll notice a few problems, namely:
- if you download each refresh timestamp separately, you're essentially reading the same document many times for no reason
- how would
HybridDataRefreshknow how to update, say,HybridCalendarif it determines it needs to update the calendar? EachHybridXshould be separate from the others (modular), so it wouldn't make sense to give it direct access to every other database.
I think those two problems highlight the need for a better spot for this to go. Maybe you can give each HybridX a .update function, and in Database.init, you can download all the refresh timestamps at once and call any .update functions that are needed.
There was a problem hiding this comment.
wait .getRefreshData() and .setRefreshData() do two different things not something for just one local and the other on the cloud. The thing is the way I currently designed it, I don't need the ability to set the dates in the cloud from the app (at least for now). Also when Database.init() is called isn't the user not signed in yet?
There was a problem hiding this comment.
wait .getRefreshData() and .setRefreshData() do two different things not something for just one local and the other on the cloud.
Of course, but the point is they're targeted at the local vs the cloud databases, and whoever uses HybridDataRefresh has to know that, which defeats the point of a HybridX class that can make the local vs cloud choice by itself.
The thing is the way I currently designed it, I don't need the ability to set the dates in the cloud from the app
So then you don't need both .get and .set in your interface, because the interface dictates what the local, cloud, and hybrid databases should share in common.
Also when Database.init() is called isn't the user not signed in yet?
Right, there are two cases:
- the user is signed in, and
Database.initcan access Firestore - the user isn't signed in,
Database.initcannot access Firestore andDatabase.signInhas to instead.
This whole feature only applies to case 1. If the user isn't signed in yet, you don't have to "refresh" anything, all the data will be downloaded in signIn. So you need an if statement in Database.init to check if the user is signed in (see the Auth class) and if so, see if the data needs to be updated.
| if(DateTime.parse(map["user"]!).isAfter(Services.instance.prefs | ||
| .getRefreshData("user")!)|| | ||
| Services.instance.prefs.getRefreshData("user")==null){ | ||
| await Services.instance.database.user.signIn(); | ||
| await Models.instance.user.init(); | ||
| print(map["user"]); | ||
| } | ||
| if(DateTime.parse(map["schedule"]!).isAfter(Services.instance.prefs | ||
| .getRefreshData("schedule")!)){ | ||
| await Models.instance.schedule.init(); | ||
| print(map["schedule"]); | ||
| } |
There was a problem hiding this comment.
- I noticed the condition isn't exactly the same between these two -- is that important?
- You made
SharedPreferences.getRefreshDatareturnDateTime?, but you immediately use!. Maybe you can make it non-nullable with a helpful error message if the right key can't be found. - The keys passed to
SharedPreferencesare simple nouns likescheduleanduser. You can imagine some future confusion because it sounds like it actually holds theuser/scheduledata. Maybe clarify by making itlast_update_scheduleoruser_last_update. - You don't want to sign in any services here, because you should already be fully signed in. You can't access Firestore unless you're already authenticated, so you can't possibly have gotten the data above and not be signed in yet.
- You don't want to be touching any models here. Services are fully initialized before the models are even touched, and that's by design. Each
XModelshould get its data from aHybridX. If you want to change the data that the model will get, make sure to change it in the database, and the model will pick it up. That's also why the cloud and local databases are separated but collected together in theHybridDatabase-- so that the model can use the local data when appropriate, and assume that it's in-sync with the cloud data. - The repetitive structure and the deep coupling (ie, that
CloudDataRefreshknows aboutHybridUserandHybridCalendarand depends on them to exist) show that you need to put this all somewhere else. See my above suggestions.
There was a problem hiding this comment.
but for the User Model isnt .signIn where it actually downloads the data?
There was a problem hiding this comment.
Oh you're right, I was confusing that with the idea of actually authenticating (maybe that means we need to rename the function, maybe like onSignIn to show it only happens after sign-in, or saveData to show that's all we're doing).
So yes, signIn would be the right function to call, but again, not from here.
There was a problem hiding this comment.
I noticed the condition isn't exactly the same between these two -- is that important?
I just forgot to add it to the other one oops
There was a problem hiding this comment.
So yes, signIn would be the right function to call, but again, not from here.
Why not from here?
There was a problem hiding this comment.
You don't want to be touching any models here. Services are fully initialized before the models are even touched, and that's by design. Each XModel should get its data from a HybridX. If you want to change the data that the model will get, make sure to change it in the database, and the model will pick it up. That's also why the cloud and local databases are separated but collected together in the HybridDatabase -- so that the model can use the local data when appropriate, and assume that it's in-sync with the cloud data.
Also don't I need to reinitialize the model the properly store the data in the right places to be presented in the UI??
There was a problem hiding this comment.
Why not from here?
Because you should be keeping the services separate, and this service is doing way too much work in other services. You're better off building this functionality directly into the other services so they can stay independent.
Also don't I need to reinitialize the model the properly store the data in the right places to be presented in the UI??
You don't re-initialize, because they haven't been initialized. The models assume that HybridX.getY() will always give the right data, and HybridX.getY assumes that the local database is up-to-date. Since the last assumption is wrong, all you need to do is just fill the local database with the updated cloud data and let RamLife handle the rest.
| late SharedPreferences _prefs; | ||
|
|
||
| /// | ||
| static String refreshData(String data) => "refresh_$data"; |
There was a problem hiding this comment.
I'd say to remove this function. It doesn't say getRefreshData, but it implies that it actually does so, when really all it does it return a string. You can probably just hard-code this string template in the actual function.
| static String refreshData(String data) => "refresh_$data"; |
There was a problem hiding this comment.
but if we eventually add another form of data that needs to be updated isn't it just easier to just leave this?
There was a problem hiding this comment.
See how I have it in my suggestion below. The "refresh_$data" part is good, because it lets you use a variable to create a unique string. I'm just saying it doesn't need to be its own function, and you can stick it into the below function.
lib/src/services/preferences.dart
Outdated
|
|
||
| /// Returns the last date the data was retrieved from the | ||
| /// database as [DateTime]. | ||
| DateTime? getRefreshData(String data){ |
There was a problem hiding this comment.
You say you return a DateTime?, but you never return null. As you saw above, that caused some confusion with the !. Instead of using ! below, handle the case where you get null and spit out an error message, and that way you can return a plain DateTime.
There was a problem hiding this comment.
but it will be null the first time the user updates the data no?
There was a problem hiding this comment.
Yep! So the ! will result in an error. That's why you should explicitly check for null and handle it properly. The ! is really a last resort, similar to dynamic in that you're telling Dart "I know this will lead to an error if it happens, but it will never happen". You shouldn't be using it to silence the errors, because those errors usually mean you need to handle the null case.
There was a problem hiding this comment.
but I do handle it within the .getRefreshData() function
There was a problem hiding this comment.
Well firstly, it's not a good idea to say you return something you don't actually ever return -- in this case, null. It's impossible for this function to ever return null. Second, you have a ! in this function, which means you don't actually handle the null case in the HybridDataRefresh service because it hits this ! before it can be returned.
lib/src/services/preferences.dart
Outdated
| final String? lastUpdate = _prefs.getString(refreshData(data)); | ||
| return DateTime.parse(lastUpdate!); |
There was a problem hiding this comment.
| final String? lastUpdate = _prefs.getString(refreshData(data)); | |
| return DateTime.parse(lastUpdate!); | |
| final String? lastUpdate = _prefs.getString(refreshData(data)); | |
| return DateTime.parse(lastUpdate!); |
Indentation
lib/src/services/preferences.dart
Outdated
| Future<void> setRefreshData(String data, DateTime value) async{ | ||
| await _prefs.setString(refreshData(data), value.toString()); | ||
| } |
There was a problem hiding this comment.
- Something I figured out quite late is that you actually don't need
async/await-- you can return theFuture<void>given to you by_prefs.setStringdirectly. - If you know you're always going to use
DateTime.now, you might as well use it here. - You shouldn't name it
databecause it doesn't actually hold any data, just the name of the data. You shouldn't name itkeyeither, since the actual key isrefresh_X. `name seems to be better. - You might as well get rid of the above
refreshDatafunction and make the string directly.
| Future<void> setRefreshData(String data, DateTime value) async{ | |
| await _prefs.setString(refreshData(data), value.toString()); | |
| } | |
| Future<void> setRefreshData(String name) => | |
| _prefs.setString("refresh_$name", DateTime.now().toString()); |
OK I know that a service might be overkill here but it is really that bad if I use. a service instead so I don't have to change it? |
|
It's not really about overkill -- overkill is kinda the name of the game around organization. It's about separation. The reason we stopped getting these super-weird bugs is because the database is completely separate from the rest of the backend, which is completely separate from the fronted. Services are meant to help with that: each service gets a chance to The way it's set up now, the refresh process is its own database that has control over every other database, which you don't really want. It's simpler to modify the definition of a |
Co-authored-by: todesj <70974284+todesj@users.noreply.github.com>
* Added schedule_search.dart * Added schedule search model * Update lib/src/models/view/schedule_search.dart Co-authored-by: Levi Lesches <levilesches@gmail.com> * Update lib/src/models/view/schedule_search.dart Co-authored-by: Levi Lesches <levilesches@gmail.com> * Fixes some formatting * Added docs and exported schedule_search.dart in lib/models.dart * Cleaned up schedule_search.dart * Cleaned up models.dart * Added Subject.id, PeriodData.name, PerioData.dayName * Update lib/src/data/schedule/subject.dart Co-authored-by: Levi Lesches <levilesches@gmail.com> * Add Period.dayName, and Period.name (Student-Lyf#81) * Converted firestore/lib/src/helpers to firestore-py/lib/utils * Ported firestore/lib/src/services to firestore-py/lib/services * Removed (and revoked) Firebase credentials * Cleanup * Cleaned up logging/Firebase issues * Added some __init__.py * Ported firebase/firestore/node/calendar.dart to firebase/firestore-py/bin/calendar.py * Ported firebase/firestore/node/admins.dart to firebase/firestore-py/bin/admins.py * Ported firebase/firestore/node/feedback.dart to firebase/firestore-py/bin/feedback.py * Added some data python files and sections python files * Continued translating more files (Note: some are incomplete!) lib/data/schedule lib/sections/reader edited lib/utils/dir to use constants from constants.yaml * Added bin/faculty, bin/secions, facultylogic and facultyreader python files Also added constants.py to get data from constants.yaml * Ported firebase/firestore/node/students.dart --> firebase/firestore-py/bin/students.py * Cleanup * Delete student.py * Fix students.py and uploading * Added some more python files. many variable should be converted to snake_case * Changed camel case variables to snake_case * small edits, opted for a different constants.py file too * Fixed some typos * Fixed bug where testers had no schedule (instead of a blank one) * --- * Finished scripts to upload data * Changed dayNames to a list rather than set * Make Fridays a Friday schedule by default * Modified feedback.py to actually output feedback * Added students reader * Cleanup * Fixed a bug in faculty/logic.py where a (Student-Lyf#101) teacher's periods weren't being added to theit schedule, instead the teacher's schedule was being set to contain only one period. Co-authored-by: todesj <josht2004@gmail.com> Co-authored-by: todesj <70974284+todesj@users.noreply.github.com> Co-authored-by: Brayden Kohler <brayk0hler@gmail.com> Co-authored-by: BraydenKO <58378705+BraydenKO@users.noreply.github.com>
* Enabled Emulator use * Fixed emulator error * Update lib/src/services/firebase_core.dart Added doc comment for `FirebaseCore.shouldUseEmulator` Co-authored-by: Levi Lesches <levilesches@gmail.com>
Makes the schedule icon appear in the dashboard when the side sheet is not persistently open
Adds Podfile.lock to the repo and updates XCode.
8cfb1f3 to
ef39a02
Compare
|
I cleaned up the commits by force-pushing. Some reviews are gone now but now it's good. |
7e7b036 to
ebd72d9
Compare
No description provided.