Showing posts with label dart. Show all posts
Showing posts with label dart. Show all posts

Monday, April 14, 2025

Making Dart test helper methods show up in the structure view of Android Studio

Why such an awkward title for this post? It is the kind of search I was doing the other day when I found no hits. I'm writing this in hopes that I can save someone else the trouble I faced when the solution is, in fact, quite simple.

My Dart project contains many unit tests, but many of them use helper methods rather than calling test directly. Unfortunately, I was deep into this approach when I realized that these tests were not showing up in Android Studio's Structure view, which is otherwise a good way to navigate files.

Here is an example to illustrate the problem. Notice that the call to test is nested within the helper function, testExample.

 import 'package:test/test.dart';  
   
 class Example {  
  bool isEven(int value) => value % 2 == 0;  
 }  
   
 void main() {  
  final example = Example();  
   
  void testExample(  
   String description, {  
   required int value,  
   required Function(bool) verify,  
  }) {  
   final result = example.isEven(value);  
   test(description, verify(result));  
  }  
   
  group('Example.isEven', () {  
   testExample(  
    '2 is even',  
    value: 2,  
    verify: (result) => expect(result, isTrue),  
   );  
  });  
 }  
   

When this is opened in Android Studio, the Structure view looks like this:

Notice how the group is empty. 

The other day, I tried different searches to find an answer. It seemed to me that there had to be some way that unit testing libraries communicated their structure to JetBrains IDEs; it could not be the case that the JetBrains engineers were doing simple string matching in source files. Yet, I had no luck. In my confusion, I even turned to ChatGPT, which confidently told me that the only way to do it would be to refactor all of my tests into (yet more) higher-order functions so that I was calling the standard test function at the top level. I asked it for a reference, hoping to find the documentation I had unsuccessfully been searching for, and it unhelpfully pointed me toward two web sites that don't address this issue. Still, I put a potential refactoring into my project plan, although with over a hundred tests and counting, and with a rather eloquent current solution, this would have been both tedious and disheartening.

One of the reasons I started writing helper methods in this particular style—that is, by sending named functions as parameters—was because this is how the bloc_test package handles testing. A day or two after having tried to search for a solution to my problem, I was in a test file and noticed that all my blocTest calls did show up in the Structure view. How was that possible? Thanks to the MIT license of bloc_test, I checked the implementation and found the @isTest annotation, which comes from the meta library. Quickly reviewing its documentation, this was clearly exactly what I needed. I included this little annotation to my project.

 import 'package:meta/meta.dart';  
 import 'package:test/test.dart';  
   
 class Example {  
  bool isEven(int value) => value % 2 == 0;  
 }  
   
 void main() {  
  final example = Example();  
   
  @isTest  
  void testExample(  
   String description, {  
   required int value,  
   required Function(bool) verify,  
  }) {  
   final result = example.isEven(value);  
   test(description, verify(result));  
  }  
   
  group('Example.isEven', () {  
   testExample(  
    '2 is even',  
    value: 2,  
    verify: (result) => expect(result, isTrue),  
   );  
  });  
 }  
   

The result was that my Structure view looked exactly how I wanted it.

There you go: the solution in Dart is to annotate the helper method with @isTest. The documentation of that annotation make it clear that it solves my problem, but I must not have hit the right keywords with my original search. I hope that this post helps anyone else who is caught by this issue.

Monday, November 25, 2024

Walking away from a November game project: A reflection on NoGaDeMon 2024, Dart, Flutter, and Bloc

I would hate to make this a tradition, but it seems that I once again entered NoGaDeMon. National Game Design Month (NaGaDeMon) is November, and for several years, I created interesting little projects during the month. Last year, I was not able to pull a project together, and I'm afraid that's the case this year as well. However, I was able to learn a bit through the attempt, so I want to capture some of it here before it slips away.

Before November, I had been tinkering with an intersection of ideas related to posts in the last few months: interactive narrative games like my The Endless Storm of Dagger Mountain, which drew from the Powered by the Apocalypse tabletop RPG space, built around some concepts from Blades in the Dark and Scum & Villainy. I figured that, for November, I would try building a very small slice of the idea. For various reasons, I also wanted to try building and releasing a game using Dart and Flutter. I dug in and started making reasonable progress for a side project.

A few days into November, John Harper released Deep Cuts, a campaign and rules expansion for Blades in the Dark. I bought a copy and was quite surprised at the rules changes. I had expected little tweaks and balancing maneuvers, but Deep Cuts actually provides a complete overhaul of the most fundamental Blades action resolution system. This was too cool not to play with, so I rehashed my planned NaGaDeMon project, essentially starting from scratch to support some of the Deep Cuts ideas.

Before last week, I was able to get a very small version of the game working, letting the player experience a single, badly written game scene. The user-interface was just awful, so in order for the game to come together would have required adding a ton of content and a complete player experience design and implementation. Both of those would be tedious efforts, especially the latter, since I am not very fast with Flutter UI development. Part of the inspiration for choosing Flutter was to gain more practice with engaging UIs. 

About two weeks ago, the work of one of my committees exploded into taking most of my unassigned work hours, and this was not altogether unexpected. We also just got the good news that we will be hosting family for several days around Thanksgiving. This will be wonderful, although it also means these won't be hobby-project days. The result is that I've decided to put this project to rest. I did learn quite a bit going this far into the project, and that is the topic for the remainder of this post.

First of all, the obvious lesson is that if I wanted to really focus on learning to make a top-notch interactive Flutter UI, I should have chosen something with zero other design risks. I knew that the best I could do in one month was to make something just functional, yet I am not sure I was honest with myself about how ugly that would likely end up. Maybe I will find a game jam that will let me get a better handle on combining turn-based game timing with implicit animations.

Prior to November, I had been tinkering with some of these design inspirations in Godot Engine, which is of course the engine I used to build The Endless Storm of Dagger Mountain. I was using a rather conventional mutable-state object-oriented architecture. I found myself frequently frustrated by the lack of good refactoring tools for GDScript. This is a significant hindrance to evolving an appropriate design. This is part of what made me switch over to Dart, which is a joy to work with in part because of the excellent tooling support from Android Studio. 

A few summers ago, I spent a great deal of time studying Filip Hracek's egamebook repository. Nothing shippable came out of my efforts—I don't think I ever even blogged about it—but I did learn a lot. I was struck by how Hracek separated the layers of his architecture, and it was the first time I spent a lot of time in a game that used immutable data models. At the time, I had looked into the Bloc architecture and struggled to make sense out of it.

Approaching this November's project, I decided to dig deeper into Bloc. I spent a lot of time with the official tutorials and puzzling over this seemingly simple diagram:


The simple tutorials are simple, which is convenient, but the more robust ones separate the "data" component into a data provider and repository. It seemed clear that the game state could be conceived of as data, but I struggled to conceptualize where the game rules should live. The game rules can be considered part of the domain model, and as such, should be separated from the bloc. This would mean that a response from the domain model may be the modified game state, which then is echoed back through the bloc to the UI with a bloc state change. However, it's also reasonable to conceive of the game state itself as the data layer and the "business logic" as being the transformations of that state. Indeed, this seems to be the difference between the simple and more complex tutorials: the simple ones deal with simple in-memory state, and the more complex ones draw data from different sources and transform them in the data layer. 

Of course, there is no silver bullet. Given the tight time constraints on the project, I simply considered the immutable game state to be my data layer, and I put the game logic in a bloc. I also simply passed the game state along to the UI, but in a more robust solution, I would have had clearer separation between layers. Including a dependency between the UI and the data layers was a matter of expedience and the intentional incurring of technical debt.

My first pass at the implementation had me writing my game states and bloc states by hand. The Equatable package meant that I didn't have to fret over writing some of the boilerplate that's necessary to do state comparisons, and it was easy to integrate this in Android Studio using Felix Angelov's Bloc plugin. When scouring the Web for help with Bloc, one quickly also comes across discussions of Freezed, which library is also integrated into Angelov's plugin. I had tinkered with Freezed in my egamebook-inspired explorations, but I have not shipped anything that uses it. After having built up my understanding of Bloc using Equatable, Freezed was an obvious next step. Next time, I would jump right into using it for cases like this.

Writing a functional Flutter user-interface was straightforward using BlocBuilder. I found this to be a convenient way to conceptualize the game, especially since it had very clear states. For example, in my original explorations (before Deep Cuts), I had the player choosing an action from a list, then customizing the action with various options from Blades in the Dark, such as pushing yourself to trade stress for dice. After rolling the dice, the player is now in a different state of the game in which they are responding to the result, such as by resisting its consequences. This was elegant to express in the code, and I am confident that with enough effort, I could make a compelling user experience out of it. By contrast, Dagger Mountain used an architecture inspired by MVP but that depended too heavily on the undocumented, unenforceable behavior of coroutines. Both of these are "only jam projects," but they are helping me to conceive of how I would approach something more significant in this problem domain. The aforementioned coroutines were my solution to synchronizing the model and view states (for example, to finish an animation before continuing to the next step of the narrative); I'm fairly certain I understand how I can do that with bloc's events and states, but since the November project will remain unfinished, there is risk.

All this exploratory coding meant that I did not follow a test-driven process. I ended up not getting into the testing libraries specifically for bloc. It's possible that this would have helped me better to conceptualize the business logic versus the domain layer, but that remains future work. 

There are still a lot of questions about the game design itself. Indeed, this entire exploration is inspired by design questions around the adaptation of Blades in the Dark tabletop gameplay into a digital experience. Citizen Sleeper is the only project I know of that has worked in this space, and it's a fantastic interpretation. I only became aware of Citizen Sleeper after I started doodling my own ideas, and it's interesting to see where they converge and where they diverge. I hope to dive back into this design space later, but for now, my attention must go toward wrapping up this semester, planning for next semester, and enjoying the upcoming Thanksgiving break.

Friday, December 9, 2022

What we learned in CS222 (Fall 2022 edition)

Regular readers will know what time it is: it's time for the next installment of What We Learned in CS222. As has been my recent custom, I moved this exercise from the final exam timeslot into the penultimate day of class, the final day of class being reserved for students' final presentations. We only had seven students in class this morning, which is less than half the enrollment but not far from what I've been getting the last several weeks.

I had them start by quickly making mind maps on the theme of programming. I only gave them three minutes to sketch them out, telling them afterward that it would be a great study exercise to compare your current map to the one made on the first day of class. The exercise was really to just prime the pump for them to answer the question, "What did you learn this semester?"

Here are the 71 responses, sorted by the number of votes earned at the end of the process. Each student had four votes, but either someone misunderstood the instruction or I lost a sticker in transit.

  • Clean Code (7)
  • TDD (6)
  • GitHub (3)
  • Code Review (3)
  • Agile fundamentals (1)
  • Version control (1)
  • User stories (1)
  • Model-View Separation (1)
  • Flutter + Dart (1)
  • Naming (1)
  • Requirements analysis (1)
  • Software architecture (1)
  • SRP
  • Acceptance testing
  • CRC cards
  • CI/CD
  • Pair programming
  • Mob programming
  • Using APIs
  • Using Android Studio / IDEs
  • Forking repositories
  • Feature branching
  • Committing in git
  • Commit messages
  • Pushing in git
  • Pulling in git
  • Licensing
  • API keys
  • Defensive programming
  • README.md
  • Pubspec.yaml
  • Achievements system
  • Pitching project ideas
  • Incremental development
  • Sprint
  • Project demos
  • Iterations
  • Breaking down epic stories
  • Emulators
  • Red-Green-Refactor
  • Comments
  • Test data files
  • Functions (especially parameters, in a Clean way)
  • Null safety
  • Async/Await
  • Stateful & Stateless widgets
  • DRY
  • gitignore
  • Reading package documentation
  • Using outside resources
  • Local saves
  • Save/Load features
  • Export/Import features
  • git commands
  • Dependencies
  • Project time management
  • File names
  • Simple, clear class names
  • Encapsulation
  • Software craftsmanship
  • Making UI buttons
  • Code side-effects
  • Delegating (in Software)
  • Delegating tasks
  • Wrappers
  • Polymorphism
  • Defensive copies
  • Text wrapping in a UI
  • Text wrapping in code
  • Test coverage
  • Non-programming skills

There are a few interesting things in this list. I was surprised that there was relatively little about Flutter and Dart in particular. I was glad to see it show up as a concept itself but more excited to see that a student recognized how very cool async and await are. Breaking down epic stories is important since just yesterday we did an in-class exercise where no one remembered that conditions of satisfaction are part of user stories, despite this being a recurring topic of the semester. Specific Clean Code ideas seem to be less represented here than in past years, more emphasis here being placed on technological issues of version control. Unfortunately, there is nothing funny in the list.

We found it easy to draw a line in the list and see that the top four items were selected by the community as the most important. I explained to them that these four will feature on the final exam, and it's up to them whether they want to share this information with their missing teammates or not. This elicited the expected nervous laughter.

Monday, July 11, 2022

A quick and dangerous tool for bulk deletion of GitHub repositories from within an organization

I use GitHub for almost all of my classes, and each class has its own named organization. Students and teams submit work through GitHub, which means that there are a lot of repositories built up in the organization by the end of the semester. I always tell my students to make their own copies or forks and then to clean their work out of the GitHub organization, but very few actually do this. Because I reuse the organizations, this leaves it up to me to clean out the repositories between semesters.

Deleting repositories by hand is tedious. For reasons I cannot explain, Repo Remover only shows a small subset of the repositories in the organization, and Repo Sweeper shows none. This morning, after taking out a few with Repo Remover, I started the process of removing repositories one at a time. Two thoughts crossed my mind: "Automate tedious manual work" and "Don't spend more time writing a program than you will save doing it by hand." Well, since I have to do this between every semester, I decided I had had enough tedium.

I've been quietly working on a Summer project using Flutter, and so after confirming that there's a nice library to wrap GitHub's API, I decided to see if I could whip something up in Dart. My major stumbling block was not realizing that a variable exported within Android Studio's terminal would not be available in the execution environment of the IDE. Once I specified my personal access token in the run configuration instead, everything worked like a charm. There's really hardly any code to it, and I've released it under GPL on GitHub at https://github.com/doctor-g/repo_deleter.

The code obviously would need to be modified for others' purposes. Right now, the "bsu-cs315" organization is hard-coded, and there's no UX to speak of. It just deletes all the repositories that start with the letter "P". Of course, I didn't start with that code. I started by having it print all the organization repositories, then I filtered out those I didn't want, and then I changed my print statement to a deletion.

Hopefully this will save me headaches next time I need to clean out an organization. Also, hopefully by writing this blog post, I will remember that I wrote a tool to make my life a little easier.

Wednesday, June 27, 2018

Noise comments in Android Studio with Flutter

Flutter sounds neat, doesn't it? I decided to take a look yesterday, and it distracted me quite a bit from my course revision work. With my course revision and reflective blog post done, I decided to get into the Flutter codelab and see what I could do.

Step one is basically to copy and paste this code into Android Studio:
OK, copy, paste, and I get this abomination:
What the...?!

Those end-of-line comments are clearly not in the code. I do not want them. They are concretely, undeniably, irrevocably bad.

My first thought was, "I need to get Android Studio not to add those comments when I paste in code." I tried Googling for variations on this, but to no avail. Maybe I was calling it the wrong thing? I searched the preferences in Android Studio but couldn't find a good match either. Not wanting to waste more time, I decided I'd just go in and manually eliminate them for now.

I cannot.

They are not actually content in the editor! Those characters are not in the file. The view of the file is showing me data that do not exist. OK, stop and think about that, think about that for a long time if you have to. If you added this feature to Android Studio in particular, think about this for a long, long, long time. This is the worst idea.

But it's not the first time I've seen it. A few versions ago, IntelliJ IDEA added a "feature" in which it would show formal parameter names at the site of the actual parameter. It looks like this:

The text "bar:" is what they call a "parameter hint." It is not selectable or erasable. It's not really there in the data—once again, the view of the file is showing something that's not in the file. When I first saw this, I was aghast, and I could feel it in my bones that bad things were coming. I make all my students turn this abomination of a feature off.

Now, we see that my intuition was right. It's getting worse. Now, the view of my Dart source code file isn't just showing data that's not there: it's rendering that data as if it were. It's not using a different font or size, as in the parameter hints.

The good news is that this helped me consider where I might turn this off, and sharing this knowledge is the primary reason for this post. If you understand a modicum of what it means to write clean and maintainable code, then go to the File Menu, open the Settings dialog, and go to the Appearance section (with the Flutter Plug-in enabled, of course).


There it is. Untick that box and accept the changes, and your source code editor will be purified.

You're welcome.