PAF #5 – LifeRecorder

There have been numerous occasions where I have been in a casual conversation with someone and gone “damn that was a good line” or “I am so going to record this for your wedding day”. A particularly infamous example I tell people about comes to mind. During a discussion about future marital prospects:

us: _what kind of girl would you like then?
_ him: any one with a hole is fine

Would have made for an interesting wedding day speech recording 🙂

Apart from blackmail material, how many times have you thought “that’s a good idea, I should write it down later” only for it to disappear in the pit of ideas I thought of but forgot and someone eventually implemented?, particularly during a conversation with someone else where pen and paper weren’t accessible? _
_

For the longest time I’ve wanted something which could easily capture moments like this; an always on recorder that you could say “save the last 60 seconds of what you heard”. Except it fell into the too crazy/difficult _to do realistically _bit till I saw Kapture and thought I wasn’t going to wear some wristband just to get it done. So here’s PAF #5 in a not so pretty picture:

liferecorder

Assuming I figure out how to do the technical bits, there’s a key difference between this PAF and the previous ones. By hook or crook, this one is definitely going on the play store.

 

Author image Min'an

PAF #4 GeoParty Show and Tell

Go try it out at http://geoparty.nimblecouchpotatoes.com ! It should work just fine if you login on your box with one name and your phone with another.

Before:

After:

It’s mostly functional 😉 It’s done using plain old setInterval ajax calls as opposed to WebSockets and since all of the command sending and message retrieval is done via REST endpoints, you could write your own external client for it.

Unfortunately I had to break some rules to get to this point (this took two fortnights), but I put it down to our trip to Iceland and London, which is another story altogether. There was already a usable frontend and backend in the first fortnight, the rest of the time was figuring out what other features to add, mostly frontendy work.

I remember talking to Scott H about the project early on and he mentioned that it sounded like there would be a significant amount of frontend work. It turned out that frontend work consumed the vast majority of the work involved, coming second only to time wasted flip flopping between ideas. Writing the backend using Go turned out to be relatively easy; it’s a simple language, you can get your head around its use of channels pretty quickly and the documentation is quite good.

Writing the frontend was a different matter altogether. You can spend hours just getting the bits and pieces aligned and working the way you want it to, but all too easily decide that some bit doesn’t quite work the way you want it to and just spend another few hours massaging it so the bits get aligned again. Doing web-based frontend work gives you a really quick feedback loop, but is consequently both a blessing and a curse; it’s easy to get lost “tidying up” or “improving” how a frontend looks when you are given free reign over how it should look. Looking at the final outcome, it would probably have taken alot less time to build if I knew what I was coming up with.

In terms of directions and ideas this could go, there’s a number of things I’d like to try if I worked on this in the future. The page itself is very general – it does room, private chat and sending of files. But it could be streamlined for specific use cases; I could imagine a “geoshare” site using the same backend that focused on sending files between people in the same location. Or richer interactions with external services; there is an unexposed API for registering webhooks that trigger on specific regular expressions, which is why you can say things like “weather in here” or “weather in sydney”. It could be hooked up to external services to provide IRC-style bot games like hangman or geo-aware wikipedia queries.

What do you think? Could this be turned into something bigger? Would love to hear your thoughts 🙂

Author image Min'an

Go Language Tour #71 – Web Crawler

Go is doing my head in. This took me a long long time to get out:

package main

import (  
    "fmt"
)

type Fetcher interface {  
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {  
    type Url struct {
        url string
        depth int
    }
    type HistoryReq struct {
        url string
        resultChan chan bool
    }

    historian := func() chan HistoryReq {
        history := map[string]bool{ url: true }
        historyReqChan := make(chan HistoryReq)
        go func() {
            for request := range historyReqChan {
                _, visited := history[request.url]
                if !visited {
                    history[request.url] = true
                } 
                request.resultChan <- visited
            }
        }()
        return historyReqChan
    }

    produce := func(urlChannel chan Url, urlRequest Url, historyReqChan chan HistoryReq, doneCounterChan chan bool) {
        getDepth, urlString := urlRequest.depth, urlRequest.url
        if (getDepth <= 0) {
            doneCounterChan <- false
            return
        }

        body, urls, err := fetcher.Fetch(urlString)
        if err != nil {
            fmt.Println(err)
        } else {
            fmt.Printf("found: %s %qn", urlString, body)
            for _, u := range urls {
                historyResultChan := make(chan bool)
                historyReq := HistoryReq{u, historyResultChan}
                historyReqChan <- historyReq
                if !<- historyResultChan {
                    urlChannel <- Url { u, getDepth - 1 }
                }                                                   
          }
        }
        doneCounterChan <- false
    }
    startWork := func(done chan bool, historyReqChan chan HistoryReq) chan Url {
        counter := 0
        doneCounterChan := make(chan bool)
        urlChannel := make(chan Url)
        go func() {
            for {
                select {
                case task := <- urlChannel:
                    counter++
                    go produce(urlChannel, task, historyReqChan, doneCounterChan)
                case <- doneCounterChan:
                    counter--
                    if counter == 0 {
                        done <- true
                    }
                }
            }
        }()
        return urlChannel
    }
    done := make(chan bool)    
    historyReqChan := historian()
    startWork(done, historyReqChan) <- Url{url, depth}

    <- done
    return
}

func main() {  
    Crawl("http://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {  
    body string
    urls []string
}

func (f *fakeFetcher) Fetch(url string) (string, []string, error) {  
    if res, ok := (*f)[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = &fakeFetcher{  
    "http://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "http://golang.org/pkg/",
            "http://golang.org/cmd/",
        },
    },
    "http://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "http://golang.org/",
            "http://golang.org/cmd/",
            "http://golang.org/pkg/fmt/",
            "http://golang.org/pkg/os/",
        },
    },
    "http://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
    "http://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
}

Go’s use of channels for synchronisation takes some getting used to :/

Here’s an earlier, less parallel version:

package main

import (  
    "fmt"
)

type Fetcher interface {  
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {  
    type Url struct {
        url string
        depth int
    }
    type History struct {
        urls map[string]bool
        lock chan bool
    }

    var produce func(urlChannel chan Url, done chan bool, history *History)
    produce = func(urlChannel chan Url, done chan bool, history *History) { 
        urlToRetrieve := <- urlChannel
        urlString, getDepth := urlToRetrieve.url, urlToRetrieve.depth
        if getDepth <= 0 {
            return
        }

        body, urls, err := fetcher.Fetch(urlString)
        if err != nil {
            fmt.Println(err)
        } else {
            fmt.Printf("found: %s %qn", urlString, body)
            <- history.lock 
            for _, u := range urls {
                if _, visited := history.urls[u]; !visited { 
                    history.urls[u] = true
                    go produce(urlChannel, done, history)
                    urlChannel <- Url { u, getDepth - 1 }
                }                                                   
            }
            select {
            case history.lock <- true:
            default:                      
                done <- true;
            }
        }
    }

    history := &History{ map[string]bool{ url: true }, make(chan bool) }
    urlChannel := make(chan Url)
    done := make(chan bool)

    go produce(urlChannel, done, history)
    urlChannel <- Url{url, depth}
    history.lock <- true

    <- done
    return
}

func main() {  
    Crawl("http://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {  
    body string
    urls []string
}

func (f *fakeFetcher) Fetch(url string) (string, []string, error) {  
    if res, ok := (*f)[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = &fakeFetcher{  
    "http://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "http://golang.org/pkg/",
            "http://golang.org/cmd/",
        },
    },
    "http://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "http://golang.org/",
            "http://golang.org/cmd/",
            "http://golang.org/pkg/fmt/",
            "http://golang.org/pkg/os/",
        },
    },
    "http://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
    "http://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
}

 

Author image Min'an

PAF #4 Geo Party – Location Based Rooms

Working from home can be a lonely experience. There isn’t anyone to have coffee with or bounce ideas off. Things you usually take for granted, like being able to catch up become alot harder when everyone else is working or in a different timezone. That’s not to say there aren’t any avenues for human interaction, going out and working in public areas is always an option.

Image courtesy of FreeDigitalPhotos.net


So close yet so far
Image courtesy of FreeDigitalPhotos.net

Even when working in public areas, you’re close to folks, but not quite either. Walking up to someone random and starting a conversation is a tricky proposition for most. Interactions with strangers is not a new idea though; mediums like IRC have been around for ages.

IRC chatrooms had some nifty features for their time. Though you had to connect to a server, you could initiate a direct chat or transfer using something called Direct-Client-to-Client. In fact, many clients supported scripts that allowed you to run bots like fileservers and trivia games.

When MSN Messenger came along, you could even do things like initiate games between clients (Solitaire showdown anyone?) which was still alot easier than what you’d probably have to do if you wanted to do a two player game on your phone today.

So here’s what I hope PAF #4 will look like.

  • Multiple folks jump online to a site, based on their location a chatroom is created or joined
  • They can chat with each other (multi party chat has been done to the death, which should help :P)
  • They can send files to each other
  • They can see in relative terms how far they are to each other (e.g. 25m away)
  • They can enter an email address or login with FB/G+ which will pull down their info/gravatar
  • They can join another chat room if they know its secret

Initially I thought of using Go, but it turns out there isn’t any free online hosting available for it. Too efficient resource utilisation maybe? So I might try to get a micro EC2 instance or switch to something else.

Author image Min'an

PAF #3 SvenskaOrd Show and Tell

You can grab a copy of the apk (~5mb) , or build it yourself from the repo . The repo’s front page also has some explanation about how to use the app.

Its appearance hasn’t changed much from the midway point, it was mostly adding new features and backporting compatibility. Yes, it is compatible with Gingerbread but at some cost.

SvenskaOrd was interesting for a number of reasons. I started off with the idea that I would be able to do the backend first, which would make TDD easier and hook it up to the frontend, where the dev cycle was slower. Coming from using Corona in BishiBashi , where changes were reflected in seconds, using the Android SDK’s native simulator felt like watching paint dry at times. The sequence of events over the fortnight went something like this:

  • Marty came up with some mockups
  • I sketched out a data model
  • In Android Studio, used TDD to build out the logic independent of Android itself. Persistence still in-memory, just enough to make tests pass [expand title=”…”]

    • Unfortunately, the new Gradle based build system doesn’t quite support the notion of unit tests. Only instrumentTests, where the tests are run in the simulator and have access to the app classes under test.
    • The fact that the simulator is involved is bad enough, but the notion of unit tests is not even supported, i.e. you have to jump through numerous hoops to get simulator-independent unit tests working (much of the build-hackery involved can now be simplified thanks to the gradle-android-test-plugin).
    • Even then, you can’t seem to run unit tests and get results natively within Android Studio, the best anyone seems to be able to do is to run a Gradle task and inspect the test report

    [/expand]

    • Started work on the UI, calling backend methods
    • Introduced ORMLite to do persistence  [expand title=”…”]

      • This is where things really started to get hairy. Testing database interactions usually requires the simulator as its associated classes require an application context. Trying to mock out the provided android framework jars in the SDK is no help because they are mostly stubs and the implementation is only available after being linked on the actual device or simulator. You know you’ve run into this when you hit ‘java.lang.RuntimeException: Stub!'.
      • Thankfully, there is an awesome project called Robolectric that provides minimal stubbed versions of core framework classes, even allowing you to shadow specific classes if you need to. This seems to be the canonical library to use for Android unit testing when framework classes are involved.

      [/expand]

      • Realise that much of Android’s provided classes for view-to-db adapters assume you’re going with the framework and using ContentProviders / SomeCursorAdapter  / CursorLoader for async database access. This is somewhat at odds with ORMs which return you object graphs. I ended up Implementing ContentProviders that return Cursors from ORMLite’s CloseableIterators. [expand title=”…”]

        • While OrmLite has support for turning its CloseableIterators to Android cursors for use in CursorAdapters, it doesn’t help cases where you need to traverse the object graph and all you have is a cursor. If you were handcrafting your own SQL, you would include all of the object graph you need in your projection so the cursor has access, but OrmLite doesn’t support projections across multiple tables; you can only select columns from a single table.
        • As an alternative, you can simply roll your own AsyncTaskLoader to retrieve object graph collections from OrmLite, but you lose the flexibility of iterable collections

        [/expand]

        • Then introduce Roboguice to try and remove much boilerplate view retrieval. And saddened to realise that it actually requires the use of Android’s support library [expand title=”…”]

          • The support library adds newer features in a way that is compatible with older Android versions. For the most part this involved changing the inheritance hierarchy and implemented interfaces from something like app.android.Activity to app.android.support.v4.Activity .
          • Initially I thought this was a good idea anyway, since the majority of devices were still on Gingerbread when I last looked a year or two ago. How things have changed – http://developer.android.com/about/dashboards/index.html . The majority of devices are now ICS (API 15) or newer. Still, it seemed like a good exercise so I went ahead

          [/expand]

          • Introduce ActionBarSherlock (ABS) for better backward compatibility as I was using the actionbar for navigation, then realise [expand title=”…”]

            • You can’t use local aars without some build-hackery
            • RoboGuice uses inheritance. ActionBarSherlock uses inheritance. Multiple inheritance is not cool. But someone’s already done the grunt work to combine the hierarchies in roboguice-sherlock
            • You might get compile and runtime goodness with older devices using the support libraries and ABS, but you cannot prevent themes from missing 🙁 It turns out that the default theme for ICS+ devices is Holo , but it’s not available on Gingerbread or Froyo.
            • The kicker is ABS used to bundle a backported version of the Holo theme so it was available, but removed the backported dialog theme in newer versions because it wasn’t relevant to the library itself. I agree with the motivation, and sure, it’s just duplicating some xml and assets, but it certainly would have been nice to offer it as a separate download since they had already done the legwork.

            [/expand]

            • Discover a bunch of bugs because I hadn’t really tested the UI bits. TDD kind of fell by the wayside when I started hooking up UI components.

          I had thought it would be a straightforward affair, given the large number of developers already on Android, but there’s still enough there that could probably span a few blogs or series of howto entries. Fleshing out entries takes time. Concrete examples and walkthroughs take time. It all just takes time, but perhaps I’ll do it as a PAF some time in the not too distant future.

          I’ll probably put it on the app store after Marty’s nutted out the bugs but do download and have a play with it too. Issue trackers are enabled on all the repositories I’ve made public 😛

Author image Min'an