inKino
inKino copied to clipboard
Code review
This is the best format I could think of, to easily get embedded code snippets and links, and some notion of "actions".
Please note that my experience with mobile development only amounts to 1 React Native app, 1 iOS App, 0.1 Android app – and I did a bit of Dart once. I might be criticising things that are well-established conventions in the mobile / Dart world 🙂. I also nitpicked a lot.
General comments
- The code looks great! Don't pay too much attention to all my other comments, it really is. It's very readable, even for someone like me who doesn't have a clue about Flutter but has looked at Dart code before, as well as JS and Java.
- I think naming is overall really good, long names where appropriate and short ones where appropriate. That goes a long way.
- I dunno if you could do a better usage of types – don't know Dart enough to judge of that.
- There is quite a bit of boilerplate code. I imagine this comes with the language / Flutter (eg. all those redefinitions of the
==
operator orhashCode
), but worth stating anyway. - I think more code could be written in a functional style (eg.
map
overforEach
+ mutating an array). There isn't that much complicated logic in the app right now that it would make a big difference, but it would definitely help with readability. - I'm surprised there isn't more reuse / abstraction for "styling" code (eg. font styles, colors, gradients, spacing). Not sure whether this is an issue, but it would seem to make it harder to ensure consistency in some of the key visual elements (animation timings, gradients, etc).
Things that are missing
I'm not sure they really are missing 😄 I'm just surprised not to see them:
- [x] A CI setup running the tests (with a badge, or link in the README)
- [x] Maybe some test coverage info or link to a collection service?
- [x] Linting tools
- [x] Some sort of script / task runner (eg.
Make
, or apackage.json
with yarn/npm in JS-land) - [x] Installation instructions
- [x] Instructions to run the tests
I imagine these might come for free with the SDK though.
In no particular order, except for this first section,
File structure & naming
-
[x] I see the
test
folder has the same structure aslib
, and test files are suffixed with_test
. Wouldn't this be easier to maintain if tests were located next to the file under test withinlib
? (I imagine there's some convention to follow here – personally I like that approach because it makes it much easier to see what has and doesn't have tests). You also might not have to do things likeFor more, see test/event_name_cleaner_test.dart.
if the test file was right there. -
[x] Same for the test data inside
test_assets
at the moment. It would make it much easier to understand the XML parsing code in saymodels/event
if the XML stub was right next to it. -
[x] The test data (https://github.com/roughike/inKino/tree/v1.0.0/test_assets) uses the
assets
terminology, but those files technically aren't bundled with the app – should usestub
instead? -
[x] I'm not sure what to think of the terminology of having
networking
andmodel
underdata
. If the API you were talking to was anything that's not understood as a data source, it would still fall undernetworking
(eg. use the same networking utils) but wouldn't necessarily bedata
. Alsomodel
feels important enough that it should be top-level – and againmodels
are more than just data (business logic) -
[x] Finally, have you considered organizing the app code by feature/domain (eg
events
,showtimes
, etc) rather than by file type / function (eg.redux
,models
)? It is sort of done already for Redux and the UI (which naturally work well by domain), so would be worth considering doing the same for the whole app IMHO.
Tests
https://github.com/roughike/inKino/blob/ead83acaecc2190978631bd7148b1331c66fd305/test/data/event_test.dart#L15-L17
- [x] I see the deserialisation tests a lot of manual assertions. I think it's fine/good for such a small test suite, but there might be a snapshot testing feature you could leverage in your test framework that would auto-generate those?
https://github.com/roughike/inKino/blob/ead83acaecc2190978631bd7148b1331c66fd305/test/redux/show/show_middleware_test.dart#L32
- [x]
sut
– system under test?. Not the clearest variable name IMHO.middleware
?
Utils
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/utils/event_name_cleaner.dart#L10-L11
- This is just a personal thing but when I make this type of regex I like to add a link to it to a service like https://regexper.com/#%28%5Cs%28%5B23%5DD%24%7C%5C%28%28%5B23%5DD%7Cdub%7Corig%7Cspanish%7Cswe%29.*%29%29 to make it easier to understand. Of course it might fall out of date compared to the actual regex. Tradeoffs 😶
Data
- [x] General comment – I notice the API response parsing code is somewhat coupled with the app's models. Generally I think it's a good idea if the app "owned" the API, but here that's not the case. Have you considered separating this out as a standalone Dart client for the Finnkino API? Since it could be reused beyond this one app and its models, this seems desirable IMHO.
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/data/models/event.dart#L95-L98
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/data/models/show.dart#L32-L36
- [x] Looks like these should be a
map
instead of aforEach
. (same comment for other models potentially)
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/data/models/theater.dart#L8-L11
- 😂 priceless
Networking
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/data/networking/tmdb_api.dart#L7-L13
- [x] I know this is documented in the README but this still strikes me as very odd. Secrets shouldn't be inlined in application code, and if the code can compile without the API key actually being set up, it should. You could consider logging a dev-time warning instead.
- [x] If you want to keep it as-is, consider adding a
tmdb_config.dart.sample
file with its content as what the README shows, and acp
command in the README. - [x] Also duped comment
UI
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/ui/common/info_message_view.dart#L15-L17
- [ ] I'm surprised to see text overflow manually handled like that. Is this idiomatic?
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/ui/theater_list/theater_list_view_model.dart#L7-L9
- Personal preference – I find it easier to understand if the view model is inlined within the view file, since the two are highly coupled.
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/ui/showtimes/showtime_list.dart#L19-L21
- [ ] Should this be "on the selected day" (or equivalent) instead of "today"?
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/ui/events/event_grid_item.dart#L37-L41
- [ ] I'm surprised text styles like this aren't extracted to some sort of "stylesheet". It would make it easier to ensure the styles are consistent?
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/ui/events/event_poster.dart#L38-L45
https://github.com/roughike/inKino/blob/a54af3d6eae6c3c7c2e8cd0ed22879c615142613/lib/ui/event_details/event_backdrop_photo.dart#L39-L48
- [ ] Same question as above – this gradient is duplicated once. Should it be extracted somewhere?
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/ui/events/event_poster.dart#L86-L88
- [ ] This looks like it's hotlinking
example.com
, but I'm not entirely sure because I don't know how the placeholder works. Either way this seems like a bad idea – is there a better fallback value?
Redux
- [ ] I would advocate for
_reducer
and_state
(and potentially_selectors
) to be co-located in the same file by default. All of this code is very tightly coupled, and is harder to follow in separate files (note: this might be true only in JS, maybe the IDE features for a language like Dart are good enough that it's less of an issue). That said, I know this isn't a very popular opinion even in JS land, and it only works if all those three things are relatively small, so take this with a grain of salt.
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/redux/app/app_middleware.dart#L32-L33
- [x] 👍 maybe mention what happens UI-wise in that scenario, so someone looking at this knows they don't have to worry.
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/redux/store.dart#L20-L22
- [x] I don't think it's idiomatic redux to have a root reducer that does more than just combining other reducers – looking at the file structure, I would not expect
app
to be the one combining all of the others.
https://github.com/roughike/inKino/blob/d8855280aac8b364b14d00fd12f293851e6876b9/lib/redux/event/event_selectors.dart#L21-L24
- [ ] Isn't there a
LinkedSet
type in Dart? - [x] I'm not 100% sure but it looks like there is no place where we would want duplicate events. Why not do this in the middleware or reducer instead of the selectors?
Did I mention the code actually is very readable?
This is by far the most insightful code review I've ever received. Thanks a lot, really!
I'll address those points when I can, most likely tomorrow.
Update: had to do a release finally, incorporated some of those changes before that. Will address all of them when I have the time. Thanks again!
🎉 let me know if there's anything that's not clear.
I'll ask if something comes up! Can I tag you as a reviewer on the upcoming Pull Requests that will address this feedback? Meaning, do you have time / interest in reviewing the changes? :)
Sure, go for it 👍
Thanks for sharing your code ! Amazing project, really !
You should take a look at https://flutter.io/cookbook/networking/background-parsing/, it could be interesting to implement it.
@anasbud Thanks, glad you like it!
I'll create an issue ticket about parsing in a separate Isolate, and see if it makes sense here. The last time I tried it, which was in January, it seemed that at least parsing JSON was fast enough. IIRC, the overhead of switching from main to background Isolate took longer than parsing the JSON, and there wasn't any UI thread stutter to begin with.
Might be that some small optimizations could add up and in this project, it would actually make a lot of sense, but you never know until you investigate. :)
Agreed. Amazing code review, and really great work! I generally concur with what's been said, and would suggest only minor changes.
Adding further comments:
- "Finally, have you considered organizing the app code by feature/domain" -- I generally agree with this sentiment. The only comment I'd make here: If you ever want to share the Redux logic with an angular app, it can be helpful to keep your "Domain Layer" and "UI Layer" separate. If not, putting everything into a single feature module can be really nice :)
- For Redux Authors (me and John): I think we should make
TypedMiddleware
a bit easier to extend. InActorMiddleware
you're extendingMiddlewareClass
and doing 1 type-check in thecall
implementation. It would be cool if you could justextend TypedMiddleware<AppState, FetchActorAvatarsAction>
and implement amiddleware
method or something with the correct type information / knowing the action has been filtered for ya. - Could you use the selector found in the
theaters
directory for this? https://github.com/roughike/inKino/blob/04667f864995eee3ece9f5f7dda58731f7e0af61/lib/redux/show/show_middleware.dart#L31 - I like the idea of putting the XML to parse in the test file as a string.
- +1 to the idea of putting tests next to the file they test. However, Dart tooling doesn't support that case very well in my experience, and the recommend approach is to put tests inside a
test
folder. I wonder if that's an issue we could file against the Dart SDK / Flutter repos? - LOL: https://github.com/roughike/inKino/blob/c7d3fc9f3eccec293b438cbac490e4761bb5c794/test/redux/actor/actor_reducer_test.dart#L14
- <3 Mockito, really nice example usage in this project
- Great demonstration of overriding the HttpClient! However, in some cases, I wonder if it makes more sense to inject an
HttpClient
orhttp.Client
into thegetRequest
function rather than relying only a static_httpClient
? If you were to extract these APIs into their own packages, you could consider using thehttp
package and this technique to make it work cross-platform (not a requirement for a purely mobile app, just a thought) - At this point I'm really just fishing for improvements. Really nice work overall :)
Thanks Brian! I'll address your feedback too when I can :)
Wow nice code review indeed.