I was recently involved with a startup as CTO, kicking off the technology side of the business. I picked Flutter as the UI technology for the phone app. The challenge was to find devs to work with that relatively new tech. There was lots of competition in the city we’d started in (pre COVID-19) for those who learned Flutter early, but globally there are a lot of people. And while we moved all developers we’d taken on to full-stack skills that would include Flutter, we needed to bootstrap our development.

What we did was place fixed-price contracts on UpWork for definable rectangles of the UI. I’d calculate how many hours it would take to code/test the rectangle were I competent with Flutter, double that and invite specific UpWork developers to the contract who I thought would be able to do it in the time estimated. I’d have to assess their experience from afar, and without questioning them too much. Indeed everything about the contract as it appeared on UpWork would need to be attractive, accurate, and easy to bid on. It didn’t matter to me where they were in the world, as I would attempt to make delivery of the work to not involve too many follow up questions. The response from the “UpWorker” would require a single question answered that would attempt to filter out the people/bots that say yes to every contract without fair consideration. I’ll not share that as I intend to keep using it without it becoming widely known.

The aid the UpWorker’s completion, and allow me/us to not have to give 24/7 attention to the resource-hogging UpWork messaging system, I would post a single script “stub” backend with the original job posting. Here is a foo/bar/baz example of that:


cat > Dockerfile <<- EOM
FROM ruby:2.4.0-alpine
RUN apk update && apk upgrade
COPY Gemfile .
RUN bundle install && bundle clean
COPY . /app
CMD ["ruby", "app.rb"]

cat > Gemfile <<- EOM
source 'https://rubygems.org'
gem 'sinatra', '~> 1.4.7'

cat > app.rb <<- EOM
require 'sinatra'
require 'json'
set :bind, ''
post '/foo' do
  data = JSON.parse request.body.read
  puts "Baz #{data['baz']}!"
  puts "Qux #{data['qux']}!"
  if data['baz'] == "abcde"
    { :result => 'waldo' }.to_json
    { :result => 'plugh' }.to_json
post '/bar' do
  data = JSON.parse request.body.read
  puts "Baz #{data['baz']}!"
  puts "Qux #{data['qux']}!"
  if data['baz'] == "waldo"
    { :result => 'quuz', "thud" => "ttttthhhhhuuuuudddddd" }.to_json
    { :result => 'quuz' }.to_json

docker build -t upwork-contract-123 .
docker run -p 4567:4567 upwork-contract-123

I have chosen Ruby’s Sinatra here for the stub web server. Ruby was not our actual backend language choice, but my prerogative here is to show the least lines possible for the REST API that I am trying to utilize, while still capture the nuances of the range or sequence of backend calls that the deliverable would need to do. I could have pointed to a Github repo for the UpWorker to look at with many more source files and directories (NodeJS, etc) but I think the Upworkers who are reviewing things to work on would subset themselves by 90% if they had to leave the UpWork UI to fully assess a (say) $100 job. So while this Sinatra/Ruby/Docker setup is contrived it is quickly assessable by candidates.

When the UpWorker finishes the work, I would replicate their success in their GitHub repo. It is a shame that UpWork doesn’t provide an impromptu Gitea/Gitlab facility for work on that platform that suits source control. After confirming that the UpWorker has satisfied the contract, I pay them. That’s true even if there are subtle bugs that I had not had the foresight to stipulate in the original contract. I might immediatly ask the UpWorker to eliminate the bug, but attach an additional fixed price fee for that. Then remember to be more specific for future contracts.

Sneakily for some of the key components (those rectangle are components of course), I might pay for two or three UpWowkers to do the same piece of work. None of these alternates would know about the others. UpWork makes that easy - one proposal generating multiple contracts where each selected UpWorker thinks they are the only one. I’ll still pay for each of those, but only pick one to copy/paste into our private codebase (which the UpWorker never saw).

The downside is that there ends up being several coding styles in your codebase, which you’ll have to refactor over time to fit your chosen standard. And this only really lends itself to new components, but it is a decent bootstrap while your existing developers learn a new UI technology. Flutter was a good choice, although the entire build toolchain is too slow. Indeed FlutterDriver’s lack of attention by the dev team is problematic to me given my relationship with Selenium specifically and test automation in general.


June 25th, 2020