sudo fry rolls /* This blog is actually mobile-friendly! */

Coding on an iPad


How it started

I am on a quest to travel quickly and carry lighter, more compact gear. In the past 2 years, my old Canon 7D DSLR setup (including the infamously bulky Sigma 50mm f/1.4) has been gradually replaced by a combination of mirrorless kit (Fujifilm X-Pro2) and film rangefinder (Bessa R2M). However, there is still a problem: I not only carry camera gear on the move, but also a laptop.

The MacBook Pro 15” (2014) is lighter than most, but it is not a small device. On a weekend trip or when I am on the move for an extended period of time, it can become quite the burden. However, I still do need to bring it along most of the time in case either duty calls or I want some more advanced tools to edit my photos. This is where the iPad Pro comes in: it’s small and light (the 10.9” version), the TrueTone 120Hz screen is marvellous, and it is a very capable writing tool when combined with a good keyboard. Switching my writing (Bear) and photo editing (Apple’s very ownPhotos) to it is easy because the flow is already established.

The only thing left is to come back to my engineering root: I want to set up a programming environment on it.

The requirements

As a software engineer, text editors and CLI software are my tools of trade. I spend most of my screen time working with either of those during a usual day of work, so I always take (a bit too much) time to evaluate and customise them. And because of my routine and how I’ve set up my programming environment everywhere else already, there are several very specific requirements that I need from this new setup.

  • I need to get my passwords from pass
  • A VPN client to provide safe passage on the treacherous parts of the Internet
  • A decent code editor
  • A GitHub/SSH client
  • Some sort of UNIX terminal

The Solution



pass has become an essential part of my life. It is encrypted, secure, simple to use, and, most importantly, lives inside the terminal. I have tried many other password managers including Apple’s very own KeyChain, but none has come near to the level of flexibility and reliability of pass.

On the iPad, I have found that the combination of Pass for iOS and its Safari extension works very well. There are several things to note during the setup process:

  • I set up the GPG key on my Mac first, then copied it to the iPad via universal clipboard.
  • For some reason ssh:// scheme does not work for me when setting up the password sync to my private GitHub repo. I had to resort to authenticating via HTTPS using a newly generated personal access token (since I have two-factor auth).
  • I highly recommend turning on Touch ID and add a password to your pass store. (formerly CloakVPN)

There is very little to complain about except that the old name is much cooler. If you have an iPhone/iPad/Mac and work remotely a lot, I highly recommend it!

It is fast, simple, and just works. In some places, the connection speed might suffer when it is turned on. But if I need to establish a secure line to a production server while on the move in the Wild West, the trade-off is definitely acceptable.


When I first attempted to build a terminal simulator on the iPad, I tried to replicated exactly what I had on the laptop. I tried several apps then quickly realised that is impossible since too many system dependencies are missing on iOS, which makes both the javascript and the python environments unusable. I got as far as setting up just ssh and then quickly gave up. At this point, I knew that I’d need a remote environment and since I had been using Termius already on my phone, I simply put it on the iPad. The question is now which remote environment I should set up.

Termius 1

Me being me, I tried another convoluted solution where I can connect remotely to my MacBook at home via a Dynamic DNS setup. I was quite happy with it since it was fast and I can run all of the scripts and tools I already had, but then I took a step back and it dawned on me that:

  • Unless I leave my MacBook at home all the time, this would not work
  • Besides, it needs to stay turned on
  • Above all, this is extremely insecure

I then built a Linux box on AWS EC2 with everything I need, slap that into a tab on Termius and voila! I can now run deployment scripts, tests and play around with our code (including nodejs and ember) on a proper working Linux environment on the go via my iPad anywhere and any time.

Termius 2

I also paid for the Pro subscription on Termius since they also offers a lot of extra features than just ssh and port forwarding:

  • Terminal tabs
  • Autocomplete
  • Agent forwarding
  • SFTP
  • Syncing configuration amongst different devices
  • Dark Mode

The last 3 items on that list are what sold me on the Pro plan.

It is also worth mentioning that as a system restriction, apps cannot stay in the background for too long (up to 3 minutes in most cases). Sometimes when working with an active terminal, we might need more than that, so Termius came up with a solution where the app asks for Always location access on the iOS device to keep the process running. It’s nothing mind-blowing but for someone who works a lot with GPS tracking and has hit similar iOS limtations many times, I find these little workarounds interesting.

Working Copy

Having worked out that the ssh /gitclient inside the terminal alone would not cut it for code reviews, I tried to find a full-blown standalone git client. Before having the iPad, I used to use iOctocat to get notifications from PRs and then review/comment on the code on it or on my MacBook. However, with the iPad, the screen is no longer tiny so I can just use GitHub’s pull request interface on Safari itself. Since iOctocat was sunset, I have reconfigured my Slack notifications to push GitHub PRs notifications to me on my iOS devices. What this means is that I can now remove push notifications from the list of required features on my git client, which unlocked a few choices. Eventually, I settled on Working Copy.

Working Copy 1

Working Copy is a git client with a simple text editor shipped along with it. It is very well designed, and the feature set is just what I need:

  • Easy integration with GitHub
  • Nice tools for resolving merge conflicts
  • Repos are fully available offline when cloned (this sounds obvious but it is surprisingly difficult to get right)
  • It is iOS 11 capable - this means that it can clone repos to iOS 11’s Files, which is a really useful feature. This means that if one wants to use a different text editor on top of it, that text editor just needs to be able to read from the same file system. It’s what I ended up doing myself because I’m not quite satisfied with the editor on Working Copy . It works just fine for things like HTML and Markdown, but that’s about it.

Working Copy 2


On the laptop, I use Sublime Text for javascript frontend, PyCharm for python backend and XCode for iOS development extensively. I did not bother setting up an XCode-equivalent on the iPad because it is not mission-critical and, to my knowledge, there is no way to set up a proper iOS development on an iOS device itself. So that left a replacement for Sublime Text and PyCharm to be figured out.

I was presented with a few recommendations on the text editors front. It came down to one of: Buffer (on 50% sale as I am writing this article), Coda, Pythonista 3 and Textastic

  • Coda ‘s price is way above the rest of the competition. To be fair, it seems to be much more feature-rich, but I don’t need the whole shebang for my kind of usage. I quickly gave it a pass.
  • Pythonista 3 is a very powerful python IDE. It offered pip integration inside its own terminal, too! But after hours of trial and error, I could not get everything our web backend needs up and running with it (namely our database software and CPython dependencies). So I decided to just go with a generic text editor and rely on my remote Linux boxes and Continuous Integration to run scripts and tests respectively.
  • Moving on, at first, I thought Buffer editorwas a steal for the price. It is sleek, and is easy to get started. But I quickly hit a couple of issues that eventually let me down:
    • Even though it supports iOS 11 Files, I can only seem to open a single file at a time. This is no good since I work with the whole project this way.
    • Okay, so if I cannot work with Working Copy like that, perhaps I might have better luck with its GitHub integration. But after using it for a while, I found myself keep coming back to Working Copy time and time again because it has much better UI for running diffs and resolving merge conflicts.

So that left Textastic as the final option…


… which checks all the tickboxes:

  • A whole project can be imported from iOS 11 Files . Working Copy actually had a way to support Textastic via some scripts before, but this just made it an order of magnitude easier
  • The design of the app is top-notch
  • It works beautifully with Working Copy so there is no need to set up a GitHub integration
  • It has Monokai theme, which I love (this is on other editors too, but I just have to mention it)


Textastic and Working Copy in split view side-by-side makes a powerful pair of tools for coding and version control. Using this with continuous integration and Termius, I can both build new code and fix bugs on both our web frontend and backend servers easily.

It is not the full stack that I normally work with since XCode is missing, but with Bitrise + fastlane + a GitHub webhook, I can always do some very simple editing and release to TestFlight easily if necessary. To run the debugger and other heavy tasks for iOS development, I would need the MacBook anyway. Furthermore, the much longer release cycle on iOS means that it isn’t really practical to set up the full dev flow on the go for XCode without the laptop.

I hope that this would be useful and save some time for someone out there looking for a development environment on a laptop substitution like I did. Get in touch with me if it does, or if you have a different opinion! Leave a comment here, drop me [a message](, find me on GitHub.

Have a nice day and happy coding!

LensCulture Magnum Photos Award 2017 Submission Review

I submitted a series of images to a LensCulture competition last year.

Hanoi: A Journey of Rediscovery

These are selected from 10 rolls of film I shot in the street during my trip to Hanoi earlier this year (2017) for the Lunar New Year.

There was not much hope in winning anyway. I gave it up very quickly after I saw the competition highlights. But each entry is given a free review, and I thought that was still worth going for.

Then I completely forgot about it. Somehow LensCulture did not notify me when the review came out either (and that was 6 months ago). I only found it today by contacting their support after wondering what the hell I paid them for after going through some of my bank statements.

In the review notes, I asked what I should do to strengthen my narratives and improve story-telling in a body of work. I was not disappointed. Here it goes:

The review:

submission images

I am moved by this work and the personal nature of going home again. To make a visual record of a place you once knew well but are retracing requires some text which you have provided. It’s especially important in a few of the photographs like the motorbike and the family with cafe and home in one. You mention wanting to strengthen your storytelling here but in my opinion, it’s already pretty good! Your commentary seems to be about the city from a little bit of distance though and I found myself wanting to see someone you knew or family or something even more personal which you may have in other frames? If so, try including it in this edit. If I want to give a single piece of advice it would be to watch your framing. Sometimes it is really exquisite which raises the bar high and then some of the other images seem like they may have been rushed or taken without consent. #8 & 9 are in the top category for me where you have photographed a real moment in time in such a way that your viewer is drawn to examine the entire image. We want to see in the photograph itself as much as possible because it is such a strong depiction of this time/place/emotion. Beautifully done! #1,5,6 and 7 are also great in the series. The few that I take exception with are #2,3 and 4 and mainly the reason is that I want to add more to each of these scenes. I want to see the context in which each of these photographs is taken. In the Dominos photograph, the sign is a major part of the image but you are skilled photographer and with the method seen in image #9, you would be able to say something that shows the old vs. new in a more striking way.

I say this because again, you have some absolutely wonderful works here and your interest in pulling them together and elevating the narrative all point to wanting to have a cohesive body of work. These comments are meant to be encouraging in that direction. I am also wondering if there is a London component here. What is the counter point to your previous life in Hanoi where you live now? There is a photographer named Alec Soth who has done some personal work with his own life story that you might take a look at. Magnum photographs has a number of members who make personal stories especially someone like Guerogui Pinkhassov. Am also appreciating that you work with film here. This technique can slow photographers down but that can be a good thing, making you consider each frame and to economize with your material in a way that digital doesn’t often make us do.

Thanks for sharing this work and I wish you much success in the future with it!

Additional Recommendations (books & photographers)

  • The Decisive Moment, by Henri Cartier-Bresson
  • Street Photography and the Poetic Image, by Alex Webb and Rebecca Norris Webb
  • Street Photography Now, by Sophie Howarth and Steve McLaren
  • 18 Composition Rules for Photos That Shine (online article)
  • Photography as Activism: Images for Social Change by Michelle Bogre

Other Resources

Relevant Quotes from Past Jurors

“I’m looking for a clear expression of an idea; I ask why is the photographer asking me to look at this? When reviewing hundreds of submissions, exceptional, well-executed work that animates an idea and is visually exciting really stands out and deserves to be recognized.” — Michael Famighetti, Editor, Aperture Magazine, New York City, USA

“A good text with the pictures is short, concise, to the point, informed, descriptive. I find it helps to imagine you are speaking to a smart child who would rather be outside playing with friends: you have very little time to tell the story and explain WHY it matters. The same holds true for captions. 1st sentence: describe the image. 2nd sentence: why and how this image fits into the body of work.” — Daphné Anglès , Picture Editor, The New York Times, Paris, France

“I am interested in pictures that educate the viewer about a topic. I’m drawn to sociopolitical landscapes or personal human dramas that can be viewed in a wider context beyond the depicted subject, as well as art that pushes the boundaries of traditional photography.” — Natasha Egan, Executive Director, Museum of Contemporary Photography, Columbia College, Chicago, IL, USA

“Every great picture tells a story and should be able to stand on its own, but viewers are often eager to know a little bit more about what the photo is about. So a simple title or caption, or a few words, can make a great photo really come to life in someone’s imagination.” — Jim Casper, Editor & Publisher of LensCulture

SVUK Leader Camp 2018 notes

Here are my personal notes from the event yesterday.

Topic: “The goal is for young leaders to be inspired from your story and go back to their VietSoc and have that mindset of connecting ppl and their community”


“Đoàn kết” (unity) in its purest sense is virtually impossible

  • Each person within a community has a different idea
  • It is impossible to please everyone
  • A good community is one that empowers its member
  • A good community is also one that has a strong core team - inspiration is contagious
  • Vietnamese communities and student communities have a great advantage: naturally we are drawn to each other
  • Politics is an inevitable part of the community building experience. Unity is built, not taken for granted
  • VietStartup is built around this principle. I also wished for Oxford to have been like this, but back in the day we never really had the vision to go out of our way to do such things even though we had more spare time. The furthest we ever got to was probably study groups or playing football
  • Tip: find a purpose for your community. When people have a common goal to work towards, it is an easier challenge

Leadership comes in many shapes and forms

  • There are leaders who are aggressive and will try to reach for the extremes, but there are also pacifiers
  • Extremes evolve the community, the very progressive & extreme leaders are perfect for this, but it is important to find a balance - the pacifier handles this.
  • VietStartup utilises this mechanism: the community needs to be always moving forward, and a constantly changing community needs pacifiers to resolve the differences and reach a consensus.
  • VOX lacked the radical leader in all of the years I was there. It might have made sense at the time, but now I think of it as a waste given the amount of potential that we had
  • Tip: give a thought to how your leadership team is structured, their personalities, assess your society needs and split your tasks accordingly

A functioning community or society is one that is actually social

  • And it should not only be so amongst its own members but also with other communities/societies
  • The reality is that for highly-purposed community like VietStartup, we can never fulfil the needs of all of our members at all times so we need to venture out
  • It is what I regretted not doing more when I was president at VOX. A prime example would be days like today where we all gather, or VietPro doing events that are impossible for VietStartup to pull off

Have fun

  • Part of the bonding experience is to be connected on different levels
  • It could be as simple as a drink after work or study groups or field trips or sports
  • Cater to what your people like and feel the most comfortable with. This not only applies to your society/community as a whole but also even more importantly to your core team.

A physical postcard in a world of virtual goods

No matter how much we advance in our technology and migrate more and more aspects of life into the virtual realm, there is still this strange attachment to physical objects. I still like keeping small photobooks of my own photographs, and I enjoy shooting film very much. To me, being able to appreciate the physical embodiment of one’s work is a magical feeling, given my complete opposite digital lifestyle.

Long story short, I whipped up an MVP for Esplorio Postcards when we were on a coding trip in Sri Lanka out of the frustration of having to find a post office, hunting for a generic card (as a photographer I want to send the gift of my own creation and experience) then trying to communicate which stamps I want. Instead, now that I’ve already had an Esplorio feed of organised pretty photos, a customised physical postcard to my loved ones is a tap and an Apple Pay touch away on the new version of Esplorio iOS app. See how easy it is:

This is why I love working in a startup - going from ideas to production is a very quick journey.

If you are interested in my work, please go ahead and check out

How TechCrunch Japan broke our app - Handling local calendars in Swift


“Timezone”, “calendar”, “region”, “locale” are 4 words that strike fear into my mind

I am writing this blog entry as a note to myself for future reference in case other people have the same problems as well, especially if you use DateTools and CoreData in your app

Time format plays a big role in our application. We not only need to be able to make requests to our backend servers with the right format (let’s say something like “get all data before 1 Jan 2016”), but also need to show the user the date in a consistent way. Now since we suddenly got a big feature in TechCrunch Japan and had an influx of new users from the other side of the planet, I thought maybe it is a good idea to switch to the Japanese calendar to see if our app still works correctly.

This uncovered a whole world of hurt given that we have not done any proper localisation yet, and are really keen on keeping Gregorian calendar as the standard across our codebase.

Problem 1

Unless specified, the default locale for any dates in the system will obey the locale of the local region. This is a well-known problem - even Apple has an official documentation here stating that you should really use en_US_POSIX if you want to keep everything consistently Gregorian. This was solved easily by setting all our date formatters to use that locale.

Problem 2

Like many other apps, we use CoreData as the backend for our client-side database and DateTools to handle many datetime-related tasks. The issue starts with NSDate instances stored in CoreData obeying the system calendar. It means this piece of code:

    let today = <A date value retrieved from CoreData>
    let formatter = NSDateFormatter()
    formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
    formatter.dateFormat = `dd MMM yyyy`

would result in 04 Jan 0028 once the phone is set to use Japanese calendar, and today will think of itself as if it is the year 0028 in Gregorian. If you then use it to perform calculations/manipulations or send it as it is to your backend API to process, it will be wrong.

The solution is to actually be using the en_US_POSIX locale but set both the default calendar identifier in DateTools and the calendar identifier on each formatter to the default system identifier. If you do not use DateTools, do make sure that whatever date/calendar solution you go with in the end does this as well!

    // The DateTools method to set default calendar
    // (do this once at app launch)

    // Rest of the code
    let today = <A date value retrieved from CoreData>
    let formatter = NSDateFormatter()
    formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
    formatter.dateFormat = `dd MMM yyyy`

It means that any NSDate instances retrieved from CoreData would always point to the right point in time, i.e. 04 Jan 0028 in Japan calendar now always points to the same point in time as 04 Jan 2016 in Gregorian calendar (having the same Unix timestamp 1451865600). Then, the effect of using the en_US_POSIX locale would in turn cause the date instance with timestamp 1451865600 being formatted into the right Gregorian format that we want: 04 Jan 2016.

Problem 3

Right, so we have solved that problem with CoreData, what is going to happen to dates created inside the app but not saved to CoreData? The answer is that it is totally messed up as well since you have done NSDate.setDefaultCalendarIdentifier(NSCalendar.currentCalendar().calendarIdentifier) at the beginning

The problem is now when creating new NSDate instances within the app, the local system calendar will be used by default

    // Notice the `NSDate()`
    let today = NSDate()
    let formatter = NSDateFormatter()
    formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
    formatter.dateFormat = `dd MMM yyyy`

The above code will result in the string 04 Jan 0028. To work around this problem, I temporarily fall back to the Gregorian calendar in the formatter for all of these dates which we need to show the user in the UI but are not created/retrieved from CoreData itself, then reset it to the system local one using defer. The full code is then:

  // A date formatter used for display views
  private static let displayDateFormatter: NSDateFormatter = {
    // Remember this following call is put here for illustration
    // In reality it should be in app launch
    let formatter = NSDateFormatter()
    // Use the default calendar identifier but with the en_US_POSIX locale
    // This will avoid weird dates from popping up
    formatter.locale = en_US_POSIX
    formatter.calendar = NSCalendar(identifier: NSDate.defaultCalendarIdentifier())
    return formatter


   Given a date (NSDate), it returns a string representation of it with only the date
  class func dateToDayDisplayString(date: NSDate, withFormat format: String,
    withTimeZone timezone: NSTimeZone? = nil, withCalendarIdentifier calendarIdentifier: String? = nil) -> String {
      if let timezone = timezone {
        displayDateFormatter.timeZone = timezone
      } else {
        displayDateFormatter.timeZone = NSTimeZone(name: "UTC")

      displayDateFormatter.dateFormat = format

      if let calendarIdentifier = calendarIdentifier {
        displayDateFormatter.calendar = NSCalendar(calendarIdentifier: calendarIdentifier)
      // This next line says always reset the calendar to the default one even after the return
      defer {
        if let _ = calendarIdentifier {
          displayDateFormatter.calendar = NSCalendar(identifier: NSDate.defaultCalendarIdentifier())
      return displayDateFormatter.stringFromDate(date)

If you are interested in my work, you can find out more about our product at