Friday, July 30, 2021

Summer Course Revisions 2021: CS222 Advanced Programming

At the time that the Fall schedule was being made, I had two outstanding grant proposals, either of which would have got me a course release for research projects. I figured I would get one or the other, so I was only assigned two courses in Fall. Well, neither came through, so the department kept their eyes on registration to see what they could assign me. About two weeks ago, I was assigned one of the sections of CS222 Advanced Programming, so this is a late addition to my summer contemplations.

This means I will have three "preps" in the Fall, which is not ideal, especially with other obligations: I am the department's P&T chair, I am on the committee that's running the CCSC:MW Conference, and I am the de facto conference chair for the Symposium on Games. I've already turned down other opportunities for fear of running myself ragged in the Fall. At least we're back to in-person classes, so it should not be quite so grueling as last Fall, when I had to teach three different classes an entirely different mode.

CS222 is a fun challenge to teach. Unfortunately, it was scheduled for MWF, which is not ideal for this class: longer, deeper sessions are much more beneficial to the students than the additional weekly contact. I do not think I can afford to give them daily assignments, which is my preference when it's a Tuesday-Thursday class, so I will have to pull back to weekly ones.

Looking over my notes from Spring, I should be able to make the changes mentioned there: including some kind of weekly check-in during the final project, and altering the "critical component" terminology. Those can both wait until the final project's articulation, so I do not have to worry about it now, only remember it. I am also looking at the summer enrichment exercise that I made for the outgoing Spring students, but I don't think I can reuse any of that for Fall. They were created to reinforce prerequisite knowledge presupposing knowledge from CS222, but of course, these incoming students won't have a clue what TDD is about. However, I do like the idea of drawing up some of the data structures content into the first three weeks of the class, especially so that I can try to determine if the same error modes are in place as were last semester, when the students clearly had not met the learning objectives of the prerequisite course.

I wrote about contemplating changes to the grading scheme in CS222, but I don't think this is the time to do that. My experience and interpretation is that the system works as designed for the vast majority of students, and that case I wrote about was a strange exception. I can catch such an exception if it comes up again.

I have already copied over the course site and made the superficial changes required. I hope to have the shell of the course site up by the end of the day, describing the first three weeks of the class.

Thursday, July 8, 2021

Refactoring the Thunderstone Quest Randomizer: Generating dart code with source_gen and build_runner

This post is part of a series about the implementation of my Thunderstone Quest randomizer. For more background, see my original post from Summer 2020 or my earlier update in Summer 2021

I remember when I first learned to use SharedPreferences to implement user settings. It was exciting to see the feature work in the Web build. I remember thinking that the implementation was a little messy, but that I could clean it up as I go. Predictably, in the excitement of adding new features, I did a lot of copy-paste coding, which led to bloat in the SettingsModel class. Some days ago, I decided to dive back into that implementation and clean it up.

If you look at the previous implementation of SettingsModel, you will see that the logic for managing user preferences is spread throughout the file. Each preference needed to add a key constant, a declaration, an accessor and a mutator, and clauses to the _loadPrefs, clear, and _updatePrefs methods. Adding the second and third ones were easy, but the more I added, the more cumbersome it became. SettingsModel also serves as a facade, hiding the fact that some preferences are handled quite differently than others.

My initial attempts at refactoring this had me spinning my wheels. Clearly, I could extract the behavior of a preference into its own class; this would just be a matter of traditional OOP. It would make sense for each of these to be a ChangeNotifier. (Check the docs for more about this important Dart class.) The SettingsModel itself could still be a ChangeNotifier that simply echoes any notification from the preference objects it contains. To set this up, I needed a way to iterate through all of the preferences. A list of preferences would do the trick, but I also needed independent fields for each preference. To reduce duplication, then, I wanted an automated way to loop through all of the Preference fields in the SettingsModel.

I assumed there would be a way to do this with runtime reflection, but I had no luck with this. I found a few references to dart:mirrors, but this does not seem to be supported in Flutter. Every time I tried to get reflection going via reflectable, I ran into a rat's nest of dependencies that I didn't understand—notably build_runner. It seems to me that, for the high quality of Flutter documentation, there was almost nothing about solving the problem I was facing.

I found a comment on a defect discussion that piqued my interest: it was a claim that anything you would want to do with reflection would instead be more idiomatically done with code generation. After several failed attempts with reflection, I started trying to better understand how to use source_gen for code generation. This video by Creative Bracket helped clarify how the pieces fit together, especially the relationship with the aforementioned build_runner.

This helped me to build a prototype that, at build time, scanned SettingsModel and added to it a list of all the preferences declared within. This was the move that gave momentum to this approach. I was able to enhance this source_gen-based approach first by generating accessors and mutators for each preference based on its declaration, and then to generalize these along with a preference type variable. In the end, this approach required no changes to the client code, which was an added bonus. 

I extracted the preferences API into its own library. It contains an abstract Preference<T> class with several implementors, including common types such as BoolPreference and IntPreference as well as special-case cases such as BrightnessPreference. Each new preference type is very simple, only having to declare the methods that are used for reading and writing to the SharedSettings object.

I have debated whether SettingsModel should be a facade or whether it should return references to the preference objects, which would allow parts of the UI to consume only those preferences that it cares about. This would lead to an awkward proliferation of producers rather than the convenient wrapping currently in SettingsModel.

The implementation is up on GitHub and licensed under GPL v3.0, so feel free to take a look if you're interested in more details.