Designing apps for the E Ink Kindle

← Back to Kevin's homepagePublished: 2018 May 16

In April 2011 I contacted Amazon about implementing mathematical typesetting algorithms for their Kindle e-reader. This didn’t end up working out (third party developers were forbidden from making “generic reader apps”), but I did end up making two games, which were released in March 2012.

If you have an older Kindle, both are still available: Collexions and Vocab Trainer.

Designing for one frame per second, grayscale

Designing for the second generation Kindle was a wonderful challenge. The screen was 600x800 grayscale pixels with a refresh rate of about one frame per second. The screen was not a touch screen — the controls included a hard-button keyboard below the screen and four page-turning buttons along the side of the device.

Kindle second generation hardware

The Kindle also included rear speakers and a headphone jack, although I never used these.

(As an aside: The Kindle is a remarkable product, in that successive generations continually remove features and functionality. The Wikipedia article shows the progression. The current, 8th generation Kindle has no speakers, no headphone jack, and no physical buttons besides power.)

Due to the refresh rate, any kind of game mechanic that relied on motion or animation was out. I tried to think about who owned Kindles, and what sorts of games or apps they might enjoy.

An early concept I explored was a cruise ship timetable app. Looking through my email archives, I’m actually quite pleased with 23 year-old Kevin’s pitch to the leading iOS app in that space:

Consider the following:

1) Who goes on cruises?

2) What’s the largest demographic of Kindle owners?

That’s right: old folks =)

The Kindle has a very different market than other mobile devices, and right now the space is wide-open—the Kindle developer program is still in beta and we’re one of the small handful of firms that have access to the Kindle SDK.

They were interested, but it turned out that there wasn’t a feasible way to get cruise ship maps and timetables under Amazon’s app size limit (~3 MB, if I recall correctly — Amazon was footing the cellular costs, and wanted to keep books/apps small).

Another app concept that a friend suggested was “medication reminder”, but I wasn’t touching that with a ten foot, lawyer-covered pole.

So I decided to make a card game, Collexions:

Collexions screenshot

This worked well with the limitations of the device, as well as the (substantial) limitations of the Kevin Lynagh illustration and visual design departments. The menus were all thrown together in Adobe Illustrator, using whatever free-to-use graphics and typefaces I could find, and rendered out as bitmaps:

Collexions screenshot

The cards themselves were drawn programmatically, and I wisely stuck with circles, triangles, and rectangles:

Collexions screenshot

Early development builds included a two-player mode, where each player could select cards using the 4x3 grid of keys on their side of the keyboard.

However, Amazon kept pushing back against this feature for inexplicable reasons. (Which became clear when the next-generation Kindle was released without the keyboard.)

Vocab Trainer

Having overcome the technical and administrative hurdles to publishing Kindle apps, I decided to quickly throw together a second one. I decided to build a spaced repetition vocabulary building game.

My reasoning was that the underlying engine could be easily reused to build a variety of content-specific apps — different languages, medical jargon, etc. This first app used Webster’s 1913 dictionary, since it was available in the public domain (i.e., no longer under copyright).

The app would prompt you with a word, then you’d press a button to see the definition:

Vocab Trainer Screenshot

You’d select whether the definition you remembered was correct, close, or completely off, and the app would schedule to show you that word/definition some period of time later.

The main menu was basically identical to that of Collexions:

Vocab Trainer Screenshot

and to fill it out with a third screen I added a “statistics” screen:

Vocab Trainer Screenshot

(Please forgive my young, clueless self for those doughnut charts.)

Tech details

Aside from the challenge of designing for a black and white, 1 FPS screen, the device itself had limited technical capabilities: A 532 MHz CPU and 32 MB of RAM.

Although the official developer kit only supported Java, I developed both games using Mirah, an experimental Ruby-like project that compiled directly into Java bytecode.

(Hosted JVM languages like Clojure, Scala, or JRuby were out of the question, since they all require several megabytes to support runtime compilation and their language-specific collections.)

Mirah was wonderful to use, and felt quite familiar since at that early point in my career the majority of my programming experience had been in Ruby and JavaScript. Here’s an example from Collexions:

class Main < AbstractKindlet

def create(context:KindletContext)
  @context = context
  @root = context.getRootContainer()
  @basedir = context.getHomeDirectory()

  #has the game ever been run before?
  @is_first_run = File.new(@basedir, ".collexions_run").createNewFile

  context.getOrientationController().lockOrientation KindleOrientation.PORTRAIT

  @root.add KLabel.new "Loading...", KTextComponent.CENTER

  #make sure that everything is repainted when the root panel is first shown.
  root = @root
  H.on_show(@root) do
    root.validate()
    KRepaintManager.paintImmediately root, true
  end

  @stopped = false
  @started = false
end

# rest of class impl ...

I don’t recall Mirah supporting runtime introspection or fancy “method missing”-style dynamic dispatch, and the coding felt much more like “CoffeeScript for Java” than Smalltalk/Ruby.

It supported all of the Java semantics I needed to interface with the Kindle API (e.g., inheriting from an abstract base class, AbstractKindlet, and annotating argument types) while avoiding the need to manually name and create lots of .java files.

Mirah automatically generated classes implementing Java’s Runnable interface from Ruby blocks, which made for easy event callbacks (e.g., the H.on_show above).

Architecturally, the apps followed the Model, View, Presenter pattern, where (roughly) a presenter class instantiates views and models and mediates their interactions by calling methods on them in response to the events they emit.

To support this architecture, most classes inherited from an event system:

class Eventable

  def initialize
    @listeners = Hashtable.new
  end

  def bind(event_name:String, block:Runnable)
    @listeners.put(event_name, HashSet.new) unless @listeners.containsKey event_name
    HashSet(@listeners.get(event_name)).add block
  end

  def trigger(event_name:String)
    if @listeners.containsKey event_name
      s = HashSet(@listeners.get event_name)
      iter = s.iterator()
      while iter.hasNext()
        begin
          Runnable(iter.next).run
        rescue
          #noop
        end
      end
    end
  end

end

Here’s a snippet from the Collexions presenter class:

class GamePresenter < Presenter

  def game_set(g:Game)
    @game.unbind_all unless @game.nil? # unbind events on previous game to prevent memory leak
    @game = g
    that = self

    @game.bind('score_changed') do
      that.update_scores
      that.clear_penalty_reason
    end

    # ... more event bindings
  end

  # ... more presenter methods
  def update_scores
    @view.update_score 0, @game.get_player_score(0)
    @view.update_score 1, @game.get_player_score(1)
  end
end

The that = self pattern should be familiar to JavaScripters — we have to capture a pointer to the presenter instance so that its methods can be called within event handler closures.

The @game.unbind_all call is also worth noting. I’m not sure what kind of garbage collector the Kindle used, nor exactly where/when garbage was generated (the developer kit didn’t ship with profiling tools), but when the Kindle’s 32 MB of RAM was exhausted, the device would simply restart. So I quickly got into the habit of explicitly unbinding event listener and otherwise freeing resources as soon as possible.

Graphics were drawn using the Java AWT library that was available on the device. To give you a flavor, here’s the paint methodfrom the Collexions CardView class:

def paint(g)
  width = getWidth
  height = getHeight
  roundness = 15
  g.setColor @white
  g.fillRect 0,0, width, height

  #draw arcs around card corners by drawing rounded rects and whiting out the middle
  if hasFocus()
    g.setColor @black
    #stroke width = 5
    5.times{|i|
      g.drawRoundRect(i,i,
                      width-2*i, height-2*i,
                      roundness, roundness)
    }
    offset = 20
    g.setColor @white
    g.fillRect(offset,0, width-2*offset, height)
    g.fillRect(0,offset, width, height-2*offset)
  end

  #draw a border around the card
  border_width = if @selected then 5 else 1 end
  g.setColor @black
  border_width.times{|i|
    g.drawRoundRect(3+i,3+i,
                    width-6-2*i, height-6-2*i,
                    roundness, roundness)
  }

 # ... and so on
end

All-in-all, both games were about 2000 lines each, with another 1000 lines for the shared event system and inevitable “util” library.

QA

Aside from the occassional out-of-memory hard restart, I never ran into any performance issues. (I guess that’s the benefit of being constrained by a one frame per second screen — it’s an easy bar for even limited hardware to meet.)

However, the platform itself was sometimes quite glitchy.

Amazon made up for it with extensive quality assurance. For example, this is one of the concerns from revision 12 of the QA test results for Vocab Trainer:

Inserting and removing the USB cable while loading the active content failed on Kindle 2nd and 3rd Generation DX:

Steps to reproduce:

1) Open the active content.

2) While loading the active content, insert the USB cable to connect the device to a computer.

3) Safely eject the device from the computer and remove the USB cable.

Actual Result:

The screen is loaded with blank spaces where the menus are displayed normally.

Expected Result:

The active content should properly resume loading.

I mean, I appreciate knowing about this bug, but the idea of USB doesn’t even exist in the app sandbox, so I’m not sure what I can do about it…

(To contrast, no human from Apple ever contacted me about my iOS weather app, much less go through 30+ email exchanges about edge case behavior and suggestions for improving learnability / UX.)

Conclusion

As you may have already guessed (seven years later), the Kindle’s “Active Content” never took off the same way apps did on iOS / Android.

The Kindle made many “paranormal romance” and fanfic millionaires; not so much in the black and white card game department — both of my apps have made a total revenue to date of $495.67. (And have a 4.5 star average review, thank you very much!)

Honestly, I’m surprised that at least three hundred people discovered that interactive apps existed on the E Ink Kindle at all — everyone I’ve mentioned this story to has responded “wait, the black and white ones have games?”

That said, I enjoyed the challenge of designing around the Kindle’s constraints.

Given Amazon’s incentives (profiting from content sales rather than hardware sales), Kindles aren’t obsoleted by software updates or fashionable marketing campaigns imploring you to get the latest model. As such, I like to imagine that there are a handful of folks out there happily matching cards or expanding their vocabulary on a decade-old device.

Thanks

Thanks to Steven Li reading a draft of this article and encouraging me to find dig up and include the code snippets.