Sidir Alfred Workflow - Improved

Slight improvement

I realised that it would probably be pretty handy to be able to search all the simulators simultaneously for an app with a certain name.

To do this simply type sidir which shows a list of all the simulators like so

sidir

now instead of drilling down by simulator you can just start typing the name of the app

multi-simulator search

BOOM - simple and it even shows what simulator each is installed on, which was harder than it might seem.

If you want to know more about how I went about implementing this change read on or just go and update your workflow and enjoy.

The longer story

So it happened - as I was commuting home I thought of a slight improvement that I wanted to make to my workflow (the above feature change). Normally this would be no biggie as I can just hack on it and get it working but...

It turns out iOS Dev Weekly liked the workflow and placed it in the weekly newsletter, which means I can't just hack on it anymore for the fear that I could be deeply embarrassed if people download it and find it not working. Whilst I can tolerate tools that break in odd ways when I am the sole user I can imagine other people are not so forgiving.

On the train I took a look at what it would take to implement the change I wanted and I realised that the current way I was doing things was not going to allow me to make this change easily. I needed to refactor a fair amount to enable me to make the change less painful.

Dang

I didn't have any tests as this was a spike of development to poke around and see what was possible. The first thing I needed was some tests to ensure that I didn't break the current flow of things which I like.

It turns out that using rexml from the Ruby standard library is great because it allows me to not require any dependencies, which makes the workflow easier for people to install. The downside is that this library seems pretty clumsy to use and it seemed to spit out it's XML in a different order for each subsequent run so I couldn't test for equality on the string output. Both of these are bad qualities during testing.

I was not concerned about what gems I used in development as long as the final workflow doesn't make use of them so a short google later and I found a gem called lorax that allows you to perform diffs on xml using nokogiri. Armed with this gem I set about writing some functional tests to cover the existing use cases, which would allow me to freely refactor.

Too many responsibilities

In the original implementation the classes that represented the search results knew too much. They:

  • Knew a lot about where they should look for files on the file system - they provided their own glob pattern
  • Knew the file location of the simulator directory ~/Library/Application Support/iPhone Simulator
  • Instances knew how to represent themselves as XML
  • Instances knew how to extract metadata from their path

It was a combination of the first 2 items that meant that the change wasn't straight forward (would have required some nasty nasty code to get things working). The 3rd item I have a plan to take care of but it wasn't required for me to do the feature.

It turns out that both of the first 2 items can be taken care of with one fix - inject this information in.

The original implementation looked something like this

SimulatorPath

def self.find root, query
  paths = []
  Dir[glob(root)].each do |path|
    result = self.new(path)
    paths << result if result.valid?(query)
  end
  paths << RootPath.new(SIMULATORS_DIR) if paths.empty?
  paths
end

def self.glob base
  "#{base}/*"
end

The flow here is

  • Line 3: Ask the concrete class SimulatorPath for the glob pattern (lines 11-13) to use for finding files and loop over the results
  • Line 4-5: Add any paths meeting the search criteria to the results
  • Line 7: Add a default item to the results if no result found

The new implementation looks more like this

ResultsFactory

def results
  items = []
  Dir[search_meta_data.glob].each do |path|
    result = search_meta_data.klass.new(path, search_meta_data.query)
    items << result if result.valid_result?
  end
      
  items << NullResult.new(root_dir, '') if items.empty?
      
  items
end

def search_meta_data
  # ugly conditionals to what query to use
  ...
  SearchMetaData.new(SimulatorResult, "#{root_dir}/*", query_components.first)
  ...
end

This looks similar but the key thing is that it has all been abstracted to outside of the SimulatorResult class.

Refactoring done

With that refactoring done and all tests green the change I wanted to actually implement was easy.

I created a new class called ApplicationResult that slightly tweaks the behavior from SimulatorApplicationResult it's superclass (great naming I know).

With the new class in place I just updated the ugly conditional used to decide which kind of query the user is performing and which result class that maps to.

This is wrapped in a handy struct to keep the information together. The sturct knows what glob to use for getting the file names on disk and then what class to send each returned path to with the correct query part.

ResultsFactory

SearchMetaData.new(ApplicationResult, "#{root_dir}/**/Applications/*", query_components.first)

Result

The net result is that if the first character of your search is not 0..9 then this new class will be used. The result on screen is only slightly different but the usability is much improved.

The visible difference is that if you have drilled down into a simulator then the results show just the name of each of the returned apps. If you have not drilled down then each result is prefixed with the simulator version number so it's always easy to distinguish what you are looking at with minimal duplication in the UI.

Effects of iOS Dev Weekly

I put up my Alfred 2 Beta workflow called sidir and didn't really expect anyone to be interested as it was just a personal tool. It turns out that being mentioned on iOS Dev Weekly can get you a fair few extra eyes to look at your stuff. Take a look at the stats for visitors to this site in the image below, if you look close enough you might be able to see the influence that being mentioned on iOS Dev Weekly has. Thanks Dave.

iOSDevWeekly influence