Last night, Morgan Nelson, who graciously served as my #pairwithme pair, and I built a browser persistent, ordered HashMap in Dart. The idea is that the projects being stored in the ICE Code Editor need to be ordered so that the most recently worked on projects are first and so that they can be accessed by project title.
The number of projects will be a few dozen at most so an inefficient lookup is not a problem. In other words, I can achieve the desired order by using a List and the lookup by scanning the list. HashMap lookup in Dart is done with the square-bracket operator. Given that I have a
projects
List of all projects, we made use of the firstWhere()
method to find a project given a title:class Store implements HashMap<String, HashMap> { // ... HashMap operator [](String key) { return projects. firstWhere( (p) => p['title'] == key, orElse: () => null ); } // ... }By default
firstWhere()
will throw an exception if no matching item is found, which is where the orElse
optional argument comes in handy.Now that we can lookup projects, we can also create and update them by defining the
[]=
operator:class Store implements HashMap<String, HashMap> { // ... HashMap operator [](String key) { return projects. firstWhere( (p) => p['title'] == key, orElse: () => null ); } void operator []=(String key, Object data) { data['title'] = key; _updateAtIndex(_indexOfKey(key), data); _sync(); } // ... }Most of the work of create-or-update is done by the private
_updateAtIndex()
method. It creates a new record at the front of the list if no matching project exists, otherwise it replaces the old project directly in the list. The most important bit in that list the
_sync()
method, which is responsible for persisting the list of projects into localStorage(). It is rather dull, given its importance: void _sync() {
window.localStorage[codeEditor] = JSON.stringify(projects);
}
Then again dull code is often the best code.Anyhow, we got that all coded and have a bunch of tests that helped drive the implementation and remain behind to catch regressions. Today I hope to catch regressions.
Next up, I want to satisfy the
dart_analyzer
, which checks the various declared types to find the following problems:➜ ice-code-editor git:(master) ✗ dart_analyzer lib/store.dart file:/home/chris/repos/ice-code-editor/lib/store.dart:18:7: Concrete class Store has unimplemented member(s) # From HashMap: bool isEmpty Iterable<String> keys Iterable<HashMap<dynamic, dynamic>> values bool containsKey(String) bool containsValue(HashMap<dynamic, dynamic>) void addAll(Map<String, HashMap<dynamic, dynamic>>) HashMap<dynamic, dynamic> putIfAbsent(String, () -> HashMap<dynamic, dynamic>) HashMap<dynamic, dynamic> remove(String) void clear() void forEach((String, HashMap<dynamic, dynamic>) -> void) 17: */ 18: class Store implements HashMap<String, HashMap> { ~~~~~This is
dart_analyzer
telling me that I have declared the ICE Store
class to be a HashMap
, but have failed to define a bunch of methods that make a HashMap
a HashMap
.This code will run perfectly fine without defining these methods—Dart does not really care. But the
dart_analyzer
is right: I have declared this as a HashMap
and people might actually want to treat it as such.I will not TDD most of these. Instead, I will delegate most to the
projects
getter and allow typing to catch problems. That is, if I define the isEmpty
getter as:class Store implements HashMap<String, HashMap> { // ... bool get isEmpty => projects.asdf; // ... }Then
dart_analyzer
will complain:➜ ice-code-editor git:(master) ✗ dart_analyzer lib/store.dart ... file:/home/chris/repos/ice-code-editor/lib/store.dart:64:32: "asdf" is not a member of ListThat is something that I can fix with:63: 64: bool get isEmpty => projects.asdf; ~~~~
class Store implements HashMap<String, HashMap> { // ... bool get isEmpty => projects.isEmpty; // ... }While working through these, my TDD by static typing catches a bug when I added a plural value instead of the intended singular:
ile:/home/chris/repos/ice-code-editor/lib/store.dart:68:48: 'Iterable<HashMap<dynamic, dynamic>>' is not assignable to 'HashMap<dynamic, dynamic>' 67: bool containsKey(key) => keys.contains(key); 68: bool containsValue(value) => values.contains(values); ~~~~~~That aside, this process goes fairly smoothly, leaving me with a fair sampling of the needed
HashMap
methods:class Store implements HashMap<String, HashMap> { // ... int get length => projects.length; bool get isEmpty => projects.isEmpty; Iterable<String> get keys => projects.map((p)=> p['title']); Iterable<HashMap> get values => projects.map((p)=> p); bool containsKey(key) => keys.contains(key); bool containsValue(value) => values.contains(value); void forEach(f) { projects.forEach((p)=> f(p['title'], p)); } // ... }The
dart_analyzer
still complains about the destructive methods:➜ ice-code-editor git:(master) ✗ dart_analyzer lib/store.dart file:/home/chris/repos/ice-code-editor/lib/store.dart:18:7: Concrete class Store has unimplemented member(s) # From Map: HashMap<dynamic, dynamic> putIfAbsent(String, () -> HashMap<dynamic, dynamic>) HashMap<dynamic, dynamic> remove(String) void clear() # From HashMap: void addAll(Map<String, HashMap<dynamic, dynamic>>) 17: */ 18: class Store implements HashMap<String, HashMap> { ~~~~~But those seem worthy of dropping down to full-blown TDD. Which is what I will do with tonight's #pairwithme pair!
Day #748
No comments:
Post a Comment