Categories
Uncategorized

While I’m looking for work, can I make a custom iOS game for you? For FREE?

Last year, I released a free game in the App Store called Aspirations Winery’s Wine Crush, or Wine Crush for short. Here’s a short video (one minute and thirty seconds) showing it in action:

The quickest way I can describe it is “Like that candy game, but with wine!”. It’s a “match 3 or more of the same item” kind of game, where you advance by earning enough points to graduate to the next level.

Here are screenshots of the first four levels:

wine crush - first 4 levels

If you’d like to play Wine Crush, go ahead — it’s free, and available right now in the App Store!
download on the app store

I wrote Wine Crush for my friends at Aspirations Winery so that they’d have a promotional tool that most wineries don’t have: their very own branded game for the iPhone and iPad. The icons in the game are wine-related, the background images are taken directly from their own wine bottle labels, and the game presents the user with lots of information about the winery. I think that they like being able to say that they have their very own branded game.

My current contract will be winding down in a couple of weeks, and I’m looking for new opportunities.

I’ve been looking at different ways to get the word out about my availability, and one of my first ideas was to create a version of Wine Crush with my own personal branding, icons and backgrounds that represented me and the kind of work I do, bundled with additional information about myself, including a resume and even a way to contact me directly from the app.

It then occurred to me that it might be more interesting if I also offered to make branded versions of the game for other people or organizations, with icons, music, background graphics, and additional information that suited them. So that’s what I’m doing — for no charge at all — for a limited number of people, for a limited time. All I ask in return is that that the app be made available for free on the App Store, with my name listed as the developer.

Would you like your own “Candy Crush”-style app, either for yourself or your organization? Are you an artist who wants a videogame platform to show off your art skills? Or would you rather simply talk to someone capable of putting together such a game who also has great communications skills and unusual promotional ideas, just to see what he can do for you? If the answer is “yes” to any of these questions, drop me a line at joey@joeydevilla.com and let’s talk!

Categories
Uncategorized

“Oblique Strategies” and Tampa iOS Meetup’s February 2017 session

This past Tuesday, I held another session of Tampa iOS Meetup, which I’ve changed to be considerably more hands-on and aimed squarely at beginning developers. This session covered building “Magic 8-Ball”- or “executive decision maker”-style apps — the kind where the user presses a button and the app presents a randomly-selected response.

For the benefit of the people who attended, for those who couldn’t make it, and for the curious, I’ll walk you through the process of coding an app called Oblique Strategies, a “press a button and get a random response” app based on Brian Eno (as in the avant-garde musician and producer) and Peter Schmidt’s (the visual artist) creativity tool.

What is Oblique Strategies?

Eno and Schmidt created Oblique Strategies in late 1974 as a tool to help artists, writers, musicians, and other creative types overcome creative blocks. The original version was a deck of cards, each with a constraint, question, or suggestion that encouraged what was then called “lateral thinking” and now called “thinking outside the box”. If you were feeling creatively blocked, you’d shuffle the deck and then draw one or more cards, each of which would say something such as:

  • Abandon one stricture.
  • Emphasize repetitions.
  • Is there something missing?
  • What would your closest friend do?
  • Use an old idea.

If you’d like to try out Oblique Strategies, there are a number of web versions: herehere, here, and here, for starters.

In this exercise, we’ll remake Oblique Strategies as an app. Here’s what the end result will look like:

The apps is pretty simple: the user taps the Get a strategy button, and a randomly-selected strategy appears below it.

Download the starter project

Rather than have you type in all the strategies and monkey around with the user interface layout, I’ve already done those things and incorporated them into a starter project that you can download and use for this coding exercise.

Click here to download the starter project.

Once you’ve downloaded the project, unzip it, which will give you a folder containing the entire project. Open that folder, then open Oblique Strategies.xcodeproj in Xcode.

Connect the view objects to the underlying view controller


The Oblique Strategies app has two user interface objects that we need to connect to the underlying code:

  1. A button, which the user presses to get a new random strategy. The button causes something to happen (that is, it executes code) in response to an event (which in this case, is the user pressing it). This requires an action connection.
  2. A label, which the app uses to display the strategy. In order to be able to change the text contents of the label, the underlying code needs to be able to refer to it using a variable. This requires an outlet connection.

Let’s start by creating the outlet connection for the label:

Do the following:

  • We need to see the main storyboard. Do this by selecting the Main.storyboard file in the project navigator.
  • The storyboard should appear. Make sure that the label that we’ll use to display the randomly-selected strategy (its default text is “Your strategy will appear here.”) is visible.
  • Turn on the assistant editor by clicking on the assistant editor button (see the screenshot above).
  • The center of your Xcode window should now be split in two, with one part still showing the main storyboard, but another part showing the underlying view controller code.
  • Control-drag from the label to the underlying code, inside the ViewController class, just below the line that reads class ViewController: UIViewController, then drop.

  • A pop-up will appear. In it:
    • Make sure that the selection in the Connection drop-down list is set to Outlet.
    • You”ll need to give the connection a name in the Name text field. Set it to strategyLabel.
    • To make the connection, click the Connect button.

  • Xcode will add a new instance variable to the class named strategyLabel. We’ll use this variable to refer to the label so that we can change its text.

Now it’s time to connect the button.

Do the following:

  • Control-drag from the button to the underlying code, inside the ViewController class, after all the other functions in the class, then drop.

A pop-up will appear. In it:

  • Make sure that the selection in the Connection drop-down list is set to Action.
  • You”ll need to give the connection a name in the Name text field. Set it to getStrategyButtonPressed.
  • You’ll need to specify the event that the connection should respond to. Select Touch Up Inside in the Event drop-down menu, which specifies that the event we want to respond to is one where the user presses down on and releases his/her finger while it’s still within the bounds of the button.
  • To make the connection, click the Connect button.

Xcode will add a new empty method to the class named getStrategyButtonPressed. We’ll use this method to carry out actions in response to the button being pressed.

We can test both the button and label connections with a single line of code. Change the contents of the getStrategyButtonPressed method so that it looks like this:

@IBAction func getStrategyButtonPressed(_ sender: Any) {
  strategyLabel.text = "You pressed the button!"
}

Run the app and press the Get a strategy button. The result should look like this:

Let’s code!

Near the top of the class, you should see a declaration that looks like this:

let strategies = [
  "Remove specifics and convert to ambiguities.",
  "Think of the radio.",
  "Don’t be frightened of clichés.",
  "Abandon one stricture.",
  "What is the reality of the situation?",
  "Simple subtraction.",
  "Are there sections? Consider transitions.",
  "Turn it upside down.",
  "Go slowly all the way round the outside.",
  "Remember: a line has two sides.",
  "Two words: Infinitesimal gradations.",
  "Make a list of everything you might do, do last item on list.",
  "Change instrument roles.",
  "Into the impossible.",
  "One word: accretion.",
  "Ask people to work against their better judgement.",
  "Disconnect from desire.",
  "Take away the elements in order of apparent non-importance.",
  "Emphasize repetitions.",
  "Don’t be afraid of things because they’re easy to do.",
  "Is there something missing?",
  "Don't be frightened to display your talents.",
  "Use unqualified people.",
  "Breathe more deeply.",
  "How would you have done it?",
  "Honor thy error as a hidden intention.",
  "Emphasize differences.",
  "Only one element of each kind.",
  "Do nothing for as long as possible.",
  "Bridges. Build? Burn?",
  "One word: water.",
  "You don’t have to be ashamed of using your own ideas.",
  "Make a sudden, destructive unpredictable action; incorporate.",
  "Tidy up.",
  "Consult other sources.",
  "Do the words need changing?",
  "Use an unacceptable color.",
  "Ask your body.",
  "Humanize something free of error.",
  "Use filters.",
  "Balance the consistency principle with the inconsistency principle.",
  "Fill every beat with something.",
  "Discard an axiom.",
  "Listen to the quiet voice.",
  "What wouldn’t you do?",
  "Is it finished?",
  "Decorate, decorate!",
  "Put in earplugs.",
  "Give the game away.",
  "Reverse.",
  "Abandon normal instruments.",
  "Trust in the you of now.",
  "Use fewer notes.",
  "What would your closest friend do?",
  "Repetition is a form of change.",
  "Two words: distorting time.",
  "Give way to your worst impulse.",
  "Make a blank valuable by putting it in an exquisite frame.",
  "The inconsistency principle.",
  "Ghost echoes.",
  "Don't break the silence.",
  "You can only make one dot at a time.",
  "Discover the recipes you are using and abandon them.",
  "Just carry on.",
  "Comrades.",
  "(Organic) machinery.",
  "Courage!",
  "What mistakes did you make last time?",
  "You are an engineer.",
  "Consider different fading systems.",
  "Remove ambiguities and convert to specifics.",
  "Mute and continue.",
  "Look at the order in which you do things.",
  "It is quite possible (after all).",
  "Go outside. Shut the door.",
  "Don't stress one thing more than another.",
  "Do we need holes?",
  "Two words: cluster analysis.",
  "Work at a different speed.",
  "Do something boring.",
  "Look closely at the most embarrassing details and amplify them.",
  "Define an area as ‘safe’ and use it as an anchor.",
  "Mechanicalize something idiosyncratic.",
  "Overtly resist change.",
  "Emphasize the flaws.",
  "Accept advice.",
  "Remember those quiet evenings.",
  "Take a break.",
  "Imagine the music as a moving chain or caterpillar.",
  "Use an old idea.",
  "Imagine the music as a set of disconnected events.",
  "Change nothing and continue with immaculate consistency.",
  "What are you really thinking about just now? Incorporate.",
  "Look at a very small object, look at its center.",
  "Not building a wall, but making a brick.",
  "The most important thing is the thing most easily forgotten.",
  "Always first steps.",
  "Question the heroic approach.",
  "Be extravagant.",
  "State the problem in words as clearly as possible.",
  "Faced with a choice, do both.",
  "Retrace your steps.",
  "Convert a melodic element into a rhythmic element.",
  "Go to an extreme, move back to a more comfortable place.",
  "Once the search is in progress, something will be found.",
  "Only a part, not the whole.",
  "From nothing to more than nothing.",
  "Be less critical more often.",
  "When is it for? Who is it for?",
  "Destroy nothing. Destroy the most important thing.",
  "Take away as much mystery as possible. What’s left?",
  "What most recently impressed you? How is it similar? What can you learn from it? What could you take from it?",
  "First work alone, then work in unusual pairs.",
  "What do you do? Now, what do you do best?",
  "Back up a few steps. What else could you have done?",
  "What were the branch points in the evolution of this entity?",
  "Try faking it.",
  "How would you explain this to your parents?",
  "Who would make this really successful?",
  "What would make this really successful?",
  "Instead of changing the thing, change the world around it.",
  "List the qualities it has. List those you'd like.",
  "What else is this like?",
  "Describe the landscape in which this belongs.",
  "Steal a solution.",
  "Assemble some of the elements in a group and treat the group.",
  "Be dirty.",
  "Lost in useless territory.",
  "Lowest common denominator.",
]

This declaration creates an array of strings named strategies. The array will act as the “deck”, from which a “card” will be randomly selected.

Let’s try displaying the last strategy in the array when the user presses the button. Change getStrategyButtonPressed() to the following:

@IBAction func getStrategyButtonPressed(_ sender: Any) {
  let lastStrategyIndex = strategies.count
  strategyLabel.text = strategies[lastStrategyIndex]
}

If you try to run the app right now, you’ll see something like this:

We’ve just encountered what’s called an “off-by-one error”. If there are x items in an array, the index of the last item isn’t x, but x-1, since array indexes start at 0.

The error is easy to fix:

@IBAction func getStrategyButtonPressed(_ sender: Any) {
  let lastStrategyIndex = strategies.count - 1
  strategyLabel.text = strategies[lastStrategyIndex]
}

Now try running the app. It should compile this time, and if you press the Get a strategy button, the app responds with the last strategy in the array:

Of course, the app would be rather uninteresting if it displayed the same result all the time. We’ll use a random number generator’s output as a way to pick a strategy from the strategies array.

Swift comes with a number of built-in random number generator functions, but the preferred general-purpose random number generator is arc4random_uniform(). It takes a positive integer n as its sole argument, and returns a pseudo-random positive integer in the range starting at 0 and ending at n – 1.

arc4random_uniform() is a little unwieldy because the argument you’re supposed to pass it is a special kind of integer: UInt32. You’ll need to convert any value you pass to it to that type. The value that arc4random_uniform() returns is also of type UInt32, which you’ll typically have to convert back into plain old Int. If you look at Swift code that uses arc4random_uniform(), you’ll often see something like this:

result = Int(arc4random_uniform(UInt32(upperBound)))

Rather than deal with all that clunkiness, let’s write a function that wraps arc4random_uniform() and frees us from having to do all those type conversions from Int to UInt32 and back again:

func randomNumber(upToButNotIncluding upperBound: Int) -> Int {
  return Int(arc4random_uniform(UInt32(upperBound)))
}

Now update getStrategyButtonPressed() so that it makes use of our new randomNumber() function:

@IBAction func getStrategyButtonPressed(_ sender: Any) {
  let numberOfStrategies = strategies.count
  strategyLabel.text = strategies[randomNumber(upToButNotIncluding: numberOfStrategies)]
}

We can even pare it down to a single line of code and make it even more concise:

@IBAction func getStrategyButtonPressed(_ sender: Any) {
  strategyLabel.text = strategies[randomNumber(upToButNotIncluding: strategies.count)]
}

We now have a properly functioning Oblique Strategies app. Run it, press the Get a strategy button, and see what strategies come up!

Add a “fade in” animation

Let’s give our app a little polish by having the strategy “fade in”. Whenever the user presses the Get a strategy button, we will:

  • Set the strategy label’s alpha (opacity) value to 0, making it completely transparent.
  • Randomly select a strategy and use it to set the text of the label.
  • Animate the label so that its alpha value is brought up to 1 over a period of 3 seconds, making it completely opaque.

Do this by updating getStrategyButtonPressed() so that it looks like this:

@IBAction func getStrategyButtonPressed(_ sender: Any) {
  strategyLabel.alpha = 0
  strategyLabel.text = strategies[randomNumber(upToButNotIncluding: strategies.count)]
  UIView.animate(withDuration: 3.0,
                 animations: {
    self.strategyLabel.alpha = 1
  })
}

Run the app. You should see the strategy fade in when you press the Get a strategy button.

All the code

If you’ve made it to this point, the code in ViewController.swift should look like this:

import UIKit


class ViewController: UIViewController {
  
  let strategies = [
    "Remove specifics and convert to ambiguities.",
    "Think of the radio.",
    "Don’t be frightened of clichés.",
    "Abandon one stricture.",
    "What is the reality of the situation?",
    "Simple subtraction.",
    "Are there sections? Consider transitions.",
    "Turn it upside down.",
    "Go slowly all the way round the outside.",
    "Remember: a line has two sides.",
    "Two words: Infinitesimal gradations.",
    "Make a list of everything you might do, do last item on list.",
    "Change instrument roles.",
    "Into the impossible.",
    "One word: accretion.",
    "Ask people to work against their better judgement.",
    "Disconnect from desire.",
    "Take away the elements in order of apparent non-importance.",
    "Emphasize repetitions.",
    "Don’t be afraid of things because they’re easy to do.",
    "Is there something missing?",
    "Don't be frightened to display your talents.",
    "Use unqualified people.",
    "Breathe more deeply.",
    "How would you have done it?",
    "Honor thy error as a hidden intention.",
    "Emphasize differences.",
    "Only one element of each kind.",
    "Do nothing for as long as possible.",
    "Bridges. Build? Burn?",
    "One word: water.",
    "You don’t have to be ashamed of using your own ideas.",
    "Make a sudden, destructive unpredictable action; incorporate.",
    "Tidy up.",
    "Consult other sources.",
    "Do the words need changing?",
    "Use an unacceptable color.",
    "Ask your body.",
    "Humanize something free of error.",
    "Use filters.",
    "Balance the consistency principle with the inconsistency principle.",
    "Fill every beat with something.",
    "Discard an axiom.",
    "Listen to the quiet voice.",
    "What wouldn’t you do?",
    "Is it finished?",
    "Decorate, decorate!",
    "Put in earplugs.",
    "Give the game away.",
    "Reverse.",
    "Abandon normal instruments.",
    "Trust in the you of now.",
    "Use fewer notes.",
    "What would your closest friend do?",
    "Repetition is a form of change.",
    "Two words: distorting time.",
    "Give way to your worst impulse.",
    "Make a blank valuable by putting it in an exquisite frame.",
    "The inconsistency principle.",
    "Ghost echoes.",
    "Don't break the silence.",
    "You can only make one dot at a time.",
    "Discover the recipes you are using and abandon them.",
    "Just carry on.",
    "Comrades.",
    "(Organic) machinery.",
    "Courage!",
    "What mistakes did you make last time?",
    "You are an engineer.",
    "Consider different fading systems.",
    "Remove ambiguities and convert to specifics.",
    "Mute and continue.",
    "Look at the order in which you do things.",
    "It is quite possible (after all).",
    "Go outside. Shut the door.",
    "Don't stress one thing more than another.",
    "Do we need holes?",
    "Two words: cluster analysis.",
    "Work at a different speed.",
    "Do something boring.",
    "Look closely at the most embarrassing details and amplify them.",
    "Define an area as ‘safe’ and use it as an anchor.",
    "Mechanicalize something idiosyncratic.",
    "Overtly resist change.",
    "Emphasize the flaws.",
    "Accept advice.",
    "Remember those quiet evenings.",
    "Take a break.",
    "Imagine the music as a moving chain or caterpillar.",
    "Use an old idea.",
    "Imagine the music as a set of disconnected events.",
    "Change nothing and continue with immaculate consistency.",
    "What are you really thinking about just now? Incorporate.",
    "Look at a very small object, look at its center.",
    "Not building a wall, but making a brick.",
    "The most important thing is the thing most easily forgotten.",
    "Always first steps.",
    "Question the heroic approach.",
    "Be extravagant.",
    "State the problem in words as clearly as possible.",
    "Faced with a choice, do both.",
    "Retrace your steps.",
    "Convert a melodic element into a rhythmic element.",
    "Go to an extreme, move back to a more comfortable place.",
    "Once the search is in progress, something will be found.",
    "Only a part, not the whole.",
    "From nothing to more than nothing.",
    "Be less critical more often.",
    "When is it for? Who is it for?",
    "Destroy nothing. Destroy the most important thing.",
    "Take away as much mystery as possible. What’s left?",
    "What most recently impressed you? How is it similar? What can you learn from it? What could you take from it?",
    "First work alone, then work in unusual pairs.",
    "What do you do? Now, what do you do best?",
    "Back up a few steps. What else could you have done?",
    "What were the branch points in the evolution of this entity?",
    "Try faking it.",
    "How would you explain this to your parents?",
    "Who would make this really successful?",
    "What would make this really successful?",
    "Instead of changing the thing, change the world around it.",
    "List the qualities it has. List those you'd like.",
    "What else is this like?",
    "Describe the landscape in which this belongs.",
    "Steal a solution.",
    "Assemble some of the elements in a group and treat the group.",
    "Be dirty.",
    "Lost in useless territory.",
    "Lowest common denominator.",
  ]
  
  @IBOutlet weak var strategyLabel: UILabel!
  
  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }

  @IBAction func getStrategyButtonPressed(_ sender: Any) {
    strategyLabel.alpha = 0
    strategyLabel.text = strategies[randomNumber(upToButNotIncluding: strategies.count)]
    UIView.animate(withDuration: 3.0,
                   animations: {
      self.strategyLabel.alpha = 1
    })
  }
  
  func randomNumber(upToButNotIncluding upperBound: Int) -> Int {
    return Int(arc4random_uniform(UInt32(upperBound)))
  }
  
}

In addition to the starter project, I’ve also posted a finished project, which you can download:

Click here to download the starter project.

You can also find this project on my GitHub.

Suggested features and further reading

Come learn iOS development at Tampa iOS Meetup!

If you’re in the Tampa area and you’ve always wanted to learn iOS development but needed some help getting started, Tampa iOS Meetup is for you! It’s a regular gathering aimed at people new to iOS development or software development in general where we cover all sorts of programming topics as we build applications together in a casual, fun, hands-on setting. Find out more at the Tampa iOS Meetup page.

 

Categories
Uncategorized

You’d be crazy to miss out on T-Mobile’s crazy (and limited-time) “free line” deal

If you have at least two active T-Mobile “lines”, you’re eligible for an additional FREE line that remains yours and free of charge for as long as you’re a T-Mobile customer. This is a limited-time offer, so I’m scurrying down to my nearby T-Mobile shop as soon as I’ve finished posting this article.

T-Mobile know that people get into all sorts of shenanigans when getting stuff for free is involved, so there are some limitations. You can’t have canceled an existing line in 2017 and then apply for the free line.

I’m off to get a line for my Moto G4 (the Android developer’s best bang for the buck), my backup and Android development phone, just so I no longer have to keep moving my SIM card between it and my iPhone.

As Chris Mills put it in BGR:

“If you’re eligible, you should sign up for the extra line just in case you ever want to add a tablet, a friend, or just leave a mobile hotspot burning data for no good reason other than that you can.”

Categories
Uncategorized

Build your first app for GM’s Next Generation Infotainment (NGI) in-car platform

When we say “internet of things,” we’re really talking about the combination of computing power and sensor technologies in places where they haven’t traditionally been, including that most traditional of everyday technologies, the car. While cars have been “hackable” for decades, only now are they getting computing platforms that third-party developers can build on: Android Auto, Apple CarPlay, and now, GM’s Next Generation Infotainment, or NGI for short.

NGI gives developers the ability to write apps for the in-car infotainment consoles located in the center dashboard of a number of GM vehicles, like the one pictured above. Using the NGI SDK, developers have access to:

  • An 800-pixel high by 390-pixel wide touchscreen to receive input from and display information to the user
  • The voice system to respond to user commands and provide spoken responses to the user
  • Data from nearly 400 sensors ranging from the state of controls (buttons and the big dial) to instrumentation (such as speed, trip odometer, orientation) to car status information (Are there passengers in the car? Are the windows open or closed?) and more.
  • The navigation system to get and set navigation directions
  • The media system to play or stream audio files
  • The file system to create, edit, and delete files on the system
  • An inter-app communication system so that apps can send messages to each other

The SDK allows you to build and test apps for GM cars on your own computer. It comes with an emulator that lets you see your apps as they would appear on the car’s display, simulate sensor readings, and debug your app with a specialized console.

Best of all, you probably already have the skills to build apps with the NGI SDK: you write apps using HTML5, CSS, and JavaScript.

Getting and installing GM’s NGI SDK

First: if you don’t already have Node.js installed on your computer, do it now. The SDK uses it (as does just about every web development framework that matters these days, so you might as well install it).

To start developing apps for GM’s new cars, go to the NGI SDK site — developer.gm.com/ngi — and register:

Once you’ve registered, log into the site, and download the latest version of the SDK install package from the downloads page:

The download will be a compressed .tgz file. Uncompress it; it will uncompress into a directory named package.

Open a terminal and install the SDK by entering the following on the command line:

npm install -g <PATH_TO_INSTALL_PACKAGE>

(…and no, don’t enter <PATH_TO_INSTALL_PACKAGE> literally — replace that with the full directory path where you downloaded the SDK install package. In my case, that full directory path was /Users/joey/Downloads/package, so I typed npm install -g/Users/joey/Downloads/package on my command line.)

Once the installer has done its dance, you can confirm that it installed successfully by entering this on the command line:

ngi --help

…which should result in the following being displayed on your terminal window:

Now you’re ready to start building NGI apps!

Getting started building NGI apps

If you’re like me, you’re probably itching to see the docs. You can fire up a local copy of the NGI documentation via the command line by entering the following:

ngi docs

This will open a new browser window that will look like this:

Starting a new project is pretty simple and not all that different from the way that many web development frameworks do it. You create a new directory, navigate to that directory, and run ngi init:

mkdir hello-gm
cd hello-gm
ngi init

This will appear in your terminal:

Answer the short list of questions that appear. You can either type in your own response, or just press Return to accept the default value (displayed in parentheses in gray text):

When you reach the “What type of network connectivity is required?” question, use the arrow keys to select the “None (all app data is local)” option. This is a simple example, after all:

When you reach the “What category of app is this?” question, use the arrow keys to select the “General” option:

After this step, you’ll have an empty project, ready for coding. You should see something like this in your terminal:

You now have a working project. You can run it in the emulator by staying in your project directory and entering this on the command line:

ngi serve

An emulator named Electron will appear. It has this icon:

and the emulator should look like this:

You’ll find the HTML source for this screen in the /src directory — it’s the index.html file. Here are its contents:

<!DOCTYPE html>
<html>
  <head>
    <title>My First App</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <!-- Base css, but you'll likely want to keep them -->
    <link rel="stylesheet" href="css/reset.css" type="text/css">

    <!-- Your custom files -->
    <link rel="stylesheet" href="css/app.css" type="text/css">

    <script src="GMLIB/system.js"></script>
    <script src="GMLIB/info.js"></script>
    <!-- Uncomment libraries as you need them: -->
    <!-- <script src="GMLIB/comm.js"></script> -->
    <!-- <script src="GMLIB/io.js"></script> -->
    <!-- <script src="GMLIB/media.js"></script> -->
    <!-- <script src="GMLIB/monitor.js"></script> -->
    <!-- <script src="GMLIB/nav.js"></script> -->
    <!-- <script src="GMLIB/phone.js"></script> -->
    <!-- <script src="GMLIB/ui.js"></script> -->
    <!-- <script src="GMLIB/util.js"></script> -->
    <!-- <script src="GMLIB/voice.js"></script> -->

  </head>
  <body>
    <div id="wrapper">
      <div id="close"><img src="images/close.png" onclick="gm.system.closeApp()" alt="close"></div>
      <div id="main">

        <!-- Remove all code inside #main and add your own! -->
        <h1>My First App</h1>
        <p>
          Your VIN is: <span id="vin"></span>
        </p>

      </div>
    </div>

    <!-- Your app code: -->
    <script src="js/app.js"></script>
  </body>
</html>

As you can see, all visible markup goes in the main div.

Let’s build a simple speedometer app. It will receive speed data and display it as shown below:

We’ll need to make changes in 3 files: HTML, CSS, and JavaScript.

Leave the emulator running, and let’s start with the HTML file, /src/index.html. Change its contents to the following:

<!DOCTYPE html>
<html>
  <head>
    <title>Hello World</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <!-- Base css, but you'll likely want to keep them -->
    <link rel="stylesheet" href="css/reset.css" type="text/css">

    <!-- Your custom files -->
    <link rel="stylesheet" href="css/app.css" type="text/css">

    <script src="GMLIB/system.js"></script>
    <script src="GMLIB/info.js"></script>
    <!-- Uncomment libraries as you need them: -->
    <!-- <script src="GMLIB/comm.js"></script> -->
    <!-- <script src="GMLIB/io.js"></script> -->
    <!-- <script src="GMLIB/media.js"></script> -->
    <!-- <script src="GMLIB/monitor.js"></script> -->
    <!-- <script src="GMLIB/nav.js"></script> -->
    <!-- <script src="GMLIB/phone.js"></script> -->
    <!-- <script src="GMLIB/ui.js"></script> -->
    <!-- <script src="GMLIB/util.js"></script> -->
    <!-- <script src="GMLIB/voice.js"></script> -->

  </head>
  <body>
    <div id="wrapper">
      <div id="close"><img src="images/close.png" onclick="gm.system.closeApp()" alt="close"></div>
      <div id="main">

        <h1>speed</h1>
        <div class="instruments">
          <div id="speed">??</div>
          <div id="units">---</div>
        </div>

        <span id="mph">mph</span>
        <label class="switch">
          <input id="unitSwitch" type="checkbox" onclick='changeUnits(this);'>
          <div class="slider round"></div>
        </label>
        <span id="kmh">km/h</span>

      </div>
    </div>

    <!-- Your app code: -->
    <script src="js/app.js"></script>
  </body>
</html>

Save the file. The emulator should now look like this:

Let’s now edit the app’s CSS file, /src/css/app.css. Change its contents to the following:

#main {
  text-align: center;
}

#close {
  position: absolute;
  top: 90px;
  right: 0;
  width: 64px;
  height: 64px;
  overflow: hidden;
  z-index: 1000;
}
#close img {
  width: 100%;
}

@media (min-width: 801px) {
  #close {
    top: 104px;
  }
}

#speed {
 font-size: 8em;
 line-height: .8em;
}

#mph {
  font-size: 2em;
}

#kmh {
  font-size: 2em;
}

/* The switch - the box around the slider */
.switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}

/* Hide default HTML checkbox */
.switch input {display:none;}

/* The slider */
.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: green;
  -webkit-transition: .4s;
  transition: .4s;
}

.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: .4s;
  transition: .4s;
}

input:checked + .slider {
  background-color: #2196F3;
}

input:focus + .slider {
  box-shadow: 0 0 1px #2196F3;
}

input:checked + .slider:before {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}

/* Rounded sliders */
.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}

Save the file. The emulator should now look like this:

And finally, let’s edit the app’s Javascript, located in /src/js/app.js. Change its contents to the following:

var useMetricUnits = true
changeUnits(document.getElementById('unitSwitch'))

function showSpeed(data) {
  console.log("showSpeed - metric? " + useMetricUnits)
  var speed = data.average_speed
  if ( speed !== undefined ) {
    var speedText = document.getElementById('speed')
    speed = useMetricUnits ? speed : Math.round(speed * 0.621)
    speedText.innerHTML = speed
  }
}

function changeUnits(checkbox) {
  console.log("Changed!" + checkbox.checked)
  useMetricUnits = checkbox.checked
  gm.info.getVehicleData(showSpeed, ['average_speed'])
}

gm.info.watchVehicleData(showSpeed, ['average_speed'])

Save the file. The emulator should now look like this (the speed reported my be different):

It’s time to simulate some car sensor data. You can do this by using the Signal Panel, which you access by clicking on this icon, located in the row of icons at the bottom of the emulator:

This window will appear:

This panel will let you simulate the nearly 400 data signals available to NGI. The one we want is called average_speed. Search for it by typing average_speed into the Find a vehicle Signal… text box:

…and click on the average_speed selection when it appears. An average_speed panel will appear, which will let you enter a speed reading via a slider or text box:

Note that average_speed reports its speeds in kilometers/hour. I’ve done the metric math for you and provided a CSS toggle switch that lets you change the display between miles and kilometers per hour.

Play around with the average_speed values, and see how that affects what you see on the screen.

If you’ve done even the smallest bit of JavaScript coding, everything in /src/js/app.js should be familiar to you except for two function calls, namely…

1. gm.info.getVehicleData(success [, failure], signals)

This method does a one-time query of the car’s systems for one or more “signal values” — that is, readings from the nearly 400 readings that the car can provide, including speed. It takes these arguments:

  • success: The method to call if the signal values can be retrieved. This method should accept a single parameter: a JavaScript object containing the retrieved values.
  • failure (optional): The method to call if the signal values cannot be retrieved.
  • signals: An array of strings containing the names of the signal values to be retrieved.

2. gm.info.watchVehicleData(success [, failure][, signals][, options])

Where gm.info.getVehicleData() does a one-time query of the car’s systems for signal values, gm.info.watchVehicleData() makes a continuous query. You can think of it as setting up a method as a “listener” for signal values. It takes these arguments:

  • success: The method to call if the signal values can be retrieved. This method should accept a single parameter: a JavaScript object containing the retrieved values.
  • failure (optional): The method to call if the signal values cannot be retrieved.
  • signals: An array of strings containing the names of the signal values to be retrieved.
  • options: An instance of the vehicleDataOptions object. One property of vehicleDataOptions is wait, which specifies the number of milliseconds between success callbacks. The default value for wait is 2000.

gm.info.getVehicleData() returns an integer value that identifies the “watch” operation that it was used to initiate. You can use that ID to cancel the “watch” operation with the gm.info.clearVehicleData(watchID) method, where watchID is the ID of the “watch” operation that you want to cancel.

And there you have it — your first NGI app!

And in case you were wondering: of course I set up a GitHub repo for this project! It’s called hello-gm and it lives on my GitHub.

Join us at the Makers Hustle Harder Hackathon in Tampa, February 27 – March 4, 2017!

If you’d like to find out more about the NGI SDK, building apps for GM’s in-car infotainment systems, and win prizes, come to the Makers Hustle Harder hackathon, which takes place in Tampa on the week of February 27th, 2017!

Tampa is the first of 3 U.S. cities where GM will be hosting Makers Hustle Harder, and it’s a chance for you to learn more about GM’s in-car IoT platform and see what you can do with it. There’ll be a kickoff meeting tonight at Tampa Hackerspace, remote work all week, and a final all-day session at Tampa Hackerspace on Saturday, March 4.

For more details, see my previous post, or visit the Tampa Hackerspace Meetup page for this event. I’ll be there — will you?

Categories
Current Events Tampa Bay Uncategorized

Try out GM’s in-car infotainment API at the “Makers Hustle Harder” hackathon in Tampa this week!

General Motors is hosting “Makers Hustle Harder” hackathon events in just three cities in the U.S., Tampa is one of them, and it’s happening this week! This is Tampa Bay developers’ chance to try out GM’s NGI (Next Generation Infotainment) API, which lets you build infotainment applications for the touchscreen interfaces on GM vehicles, with access to real-time data from over 350 data sources.

Makers Hustle Harder is an all-week event that starts with a kickoff meeting on Monday, February 27 at 6:00 p.m. at Tampa Hackerspace. That’s when teams (2 to 4 developers per team) will be finalized and participants will get an introduction to the hackathon, as well as NGI.

From Tuesday, February 28th through Friday, March 3rd, teams will work remotely on the their projects. Participants will be able to get live support from the GM teams from 6:00 p.m. through 9:00 p.m. on those days.

The final day of the hackathon will be an in-person event at Tampa Hackerspace on Saturday, March 4th from 9:00 a.m. through 6:00 p.m., with people putting the finishing touches on their projects and making final pitches at 4:00 p.m..

The grand prize will be a trip to GM headquarters in Detroit for all the members of the winning team. There will also be prizes for runners-up.

GM’s NGI SDK in action. Click the photo to read TechCrunch’s story on it.

Apps written using the NGI SDK are written on Node.js using HTML5, CSS, and JavaScript, and run on 8-inch (diagonal) touchscreen in GM vehicles. GM’s native APIs give developers access to all sorts of car info, including:

  • Instrument panel measurements, such as trip odometer, orientation, and vehicle speed
  • GPS and navigation data
  • Audio playback and streaming
  • Status information, such as presence of passengers or if the windows are open or closed
  • Vehicle features, such as radio or backup camera
  • Performance and maintenance data, such as oil life and tire pressure
  • Warning indicators, such as a burnt-out lightbulb or low washer fluid
  • Internet data via OnStar’s 4G LTE

The NGI SDK also has a system that simulates real vehicle data so that you can test your apps on your development machine.

GM’s Director of Application Ecosystem and Development Ed Wrenbeck says that the NGI SDK makes it possible for developers to create apps ready for testing in as little time as a week. He also says that the API opens up a world of possibilities: “If you were somebody like a map provider, for example, you could actually read the suspension data coming off the vehicle and use it to determine where potholes were at in the street, for example. Just one example of some of the unusual ways that you can use data that GM provides uniquely, that other OEMs just don’t provide via their infotainment systems.”

Heavy Metal Racing, an NGI-based racing videogame that uses the Corvette’s steering wheel as a controller.

Here’s a video showing highlights from an earlier NGI hackathon:

How to participate

  1. Make sure you register for the hackathon at the official registration page.
  2. It would also help with planning if you RSVP at the Meetup.com pages for Monday’s kickoff meeting and Saturday’s full-day event.
  3. Get GM’s NGI SDK and documentation from their developer site.
  4. Assemble a team beforehand or find a team that needs developers at Monday’s kickoff. Each team must have at least one representative present at the kickoff.
Categories
Uncategorized

How software projects are managed

The earliest instance of this that I can find is from Mikko Leskelä’s Twitter feed.

I’ve heard a lot of jokes about building software, but this one’s new to me! And yes, it’s funny because it’s true — we’ve all seen projects where the one thing that seems to be set in stone is the delivery date.

Categories
Uncategorized

The Uber story that everyone’s talking about right now, and some helpful background info

If you haven’t read Susan Fowler’s article, Reflecting on One Very, Very Strange Year at Uber, open a new tab and read it now. She worked at Uber as a site reliability engineer, when site reliability engineering was a new team at the company, and “things were just chaotic enough that there was exciting reliability work to be done.” It’s a story that starts with great promise, and devolves into one of harassment, circling the wagons boy’s club-style, and an HR department even more abominable than the stereotypical one.

Among her experiences, she lists:

  • A manager was asking women working at Uber for sex. “On my first official day rotating on the team, my new manager sent me a string of messages over company chat. He was in an open relationship, he said, and his girlfriend was having an easy time finding new partners but he wasn’t.”
  • The manager wasn’t disciplined because it was a “first offence” and he was a “high performer”. “When I reported the situation, I was told by both HR and upper management that even though this was clearly sexual harassment and he was propositioning me, it was this man’s first offense, and that they wouldn’t feel comfortable giving him anything other than a warning and a stern talking-to.”
  • Fowler talked to other women at Uber and found they’d be asked for sex by the same manager. “It became obvious that both HR and management had been lying about this being ‘his first offense,’ and it certainly wasn’t his last.”
  • She tried to transfer to another department, but her transfer was blocked because of her “undocumented performance problems”. “I pointed out that I had a perfect performance score, and that there had never been any complaints about my performance. I had completed all OKRs on schedule, never missed a deadline even in the insane organizational chaos, and that I had managers waiting for me to join their team … I kept pushing, until finally I was told that ‘performance problems aren’t always something that has to do with work, but sometimes can be about things outside of work or your personal life.’”
  • Her manager threatened her with dismissal for reporting issues to HR. “California is an at-will employment state, he said, which means we can fire you if you ever do this again. I told him that was illegal, and he replied that he had been a manager for a long time, he knew what was illegal, and threatening to fire me for reporting things to HR was not illegal. I reported his threat immediately after the meeting to both HR and to the CTO: they both admitted that this was illegal, but none of them did anything. (I was told much later that they didn’t do anything because the manager who threatened me ‘was a high performer’).”

These are just the “highlights” of her story; you should really read the whole thing.

Before you dismiss her story…

There will be people who will dismiss Fowler’s claims, saying things like: “I’ve seen women in tech that accused their male-dominated teams in sexism just because they couldn’t keep up with the rest of team”. That’ll be a hard claim to make of Fowler, as she now works for Stripe, who have a pretty thorough vetting process (I’ve been through it), and considering that she’s taken her skill and experience at Uber and distilled it into the O’Reilly book Production-Ready Microservices.

Uber’s CEO promises to go after the real culprits

Y’know, just like this guy promised to devote his life to going after his wife’s real killer.

Months of talking to Uber HR did her no good, but posting an article on her blog got a quick reaction from Uber CEO Travis Kalanick, who released this statement:

“I have just read Susan Fowler’s blog. What she describes is abhorrent and against everything Uber stands for and believes in. It’s the first time this has come to my attention so I have instructed Liane Hornsey our new Chief Human Resources Officer to conduct an urgent investigation into these allegations. We seek to make Uber a just workplace and there can be absolutely no place for this kind of behavior at Uber — and anyone who behaves this way or thinks this is OK will be fired.”

It’s a lovely statement, but you’ve got to take into account things that Kalanick has said, the kind of company culture he’s engendered (remember, company culture is largely a top-down thing), and the things that execs have done under that culture, including casually threatening journalists with smear campaigns.

Uber board member Arianna Huffington tweeted this announcement soon after:

“No coincidence”

Here’s a notable tweet in response to Rigetti Fowler’s article:

In case you were wondering, the photo of the sour-looking portly gentleman is of Auric Goldfinger from the James Bond Book and film, who has this to say about coincidences:

“Once is happenstance. Twice is coincidence. Three times is enemy action.”

Let’s replace enemy action with established patterns of behavior at Uber, and look at what’s publicly known about the company, shall we?

But that was just dinner party talk!

At a dinner party in November 2014 thrown by Uber at a time when they were trying to convey to the public that they weren’t as bad as the media made them out to be, Uber Senior VP Business Emil Michael suggested countering negative reports with a smear campaign. He suggested spending “a million dollars” to hire opposition researchers and journalists to look into “your personal lives, your families” and as Buzzfeed (who had an editor in attendance at that shindig) puts it: “give the media a taste of its own medicine.”

When someone at the dinner countered that a plan like that would be a problem for Uber, Michael replied: “Nobody would know it was us.”

Ben Smith, Buzzfeed’s Editor-in-Chief  wrote:

Michael was particularly focused on one journalist, Sarah Lacy, the editor of the Silicon Valley website PandoDaily, a sometimes combative voice inside the industry. Lacy recently accused Uber of “sexism and misogyny.” She wrote that she was deleting her Uber app after BuzzFeed News reported that Uber appeared to be working with a French escort service. “I don’t know how many more signals we need that the company simply doesn’t respect us or prioritize our safety,” she wrote.

At the dinner, Michael expressed outrage at Lacy’s column and said that women are far more likely to get assaulted by taxi drivers than Uber drivers. He said that he thought Lacy should be held “personally responsible” for any woman who followed her lead in deleting Uber and was then sexually assaulted.

Then he returned to the opposition research plan. Uber’s dirt-diggers, Michael said, could expose Lacy. They could, in particular, prove a particular and very specific claim about her personal life.

Michael ended up having to apologize to Sarah Lacy publicly…

…but when he called her to talk off the record and explain his comments, she quite astutely refused: “You threaten my family and you want to chat off record? Um no.”

If you check LinkedIn, you’ll see that Michael is still Uber’s SVP of Business.

When leaving a highly-valued company and a whole lot of un-vested stock options seems like the better option

In a July 2016 article in Pando, Sarah Lacy wrote about the departure of Renee Atwood, who was then the head of Uber HR:

Renee Atwood is either the least self interested person in Silicon Valley or she knows something about Uber the rest of us don’t.

Until Wednesday, Atwood was the head of HR for Uber, a job she’d taken after four years at Google. Hers should have been the easiest job in Silicon Valley: Head of HR for the highest valued, more anticipated IPO candidate we’ve seen since Facebook. The unicorn-iest unicorn in an era of more than 150 unicorns.

She joined Uber in 2014– when the company’s valuation was a “mere” $13 billion, compared to near $70 billion today. Smart bet. When she joined Uber had just 600 employees– what Google had in year five. Today it has more than 5,000. By all metrics she seems to have done an amazing job in a period of insane hyper growth.

So let’s recap:

  • After years at Google Atwood jumps to the next hottest thing: Check!
  • In just two years the value of the company she joined has jumped from $13 billion to close to $70 billion indicating a massive, massive payday coming for her: Check!
  • She’s the gatekeeper between every young aggressive candidate who wants to be part of the hottest company in all startups and her only job is to vet the right ones: Check!

That story sounds like a senior manager’s Silicon Valley dream.

And then what happened Wednesday? Atwood announced she was leaving Uber…. For Twittter.

Twitter!

Not Airbnb. Not even a troubled decacorn like Dropbox or Pinterest. Not even a still surging goliath like Facebook.

Twitter. The company that can’t seem to keep a head of product. The place “with no plan B.” Atwood left what should be the easiest job in HR in Silicon Valley for what should be one of the hardest. It’s like leaving as Facebook’s head of HR just before the IPO to go to Yahoo. Even without knowing the specifics of her contract at either place it’s safe to say Renee Atwood just turned down a major potential payday for one of the most thankless jobs in tech.

Sources close to the situation confirm that Atwood was not pushed. She was recruited away by Twitter. The sound you just heard was Uber CEO Travis Kalanick punching the wall in his famous “war room.”

Where to start with the news other than that it’s a colossally bad sign for Uber?

“A couple of female engineers…they don’t do much hardcore backend work or anything”

Here’s a tweet from 2015:

The person who tweeted deleted their original account and now has a protected one, because the whistleblowing in the tech and corporate worlds seems to follow this pattern:

  1. The whistle is blown.
  2. People rally around the whistleblower at first.
  3. Soon, the “Did the whistleblower go too far?” stories appear.
  4. And then, everyone attacks the whistleblower.

Dear Uber recruiter…

Tess Rinearson, an engineer at the blockchain network-building company Chain, was getting an email from an Uber recruiter often enough that she has a boilerplate response that reads as follows:

Thanks for reaching out. I really do appreciate that you took the time to check out my writing, and I’m sure the technical problems you’re solving are genuinely interesting.

However, I am a woman, and Uber’s track record on women scares me. The latest, on women who have been assaulted via Uber, felt particularly jarring. But also: The gendered attacks on a prominent woman in tech, the sexualized ads in France

I just don’t think I’d be a culture fit.

(And, as a backend engineer, it doesn’t seem like a place I’d get a lot of respect, either.)

Alex Kras’ interview at Uber

It opens with…

A little less than a year ago I had the “pleasure” to interview at Uber. I didn’t know much about the company at the time, other than that they were big. I was looking for work already and Uber’s recruiter reached out to me, so I figured to give it a shot.

…and closes with:

Hopefully Uber will be able to grow up into a professional and respectful place of employment. Until then I am sticking with Lyft, even if it costs me a few dollars extra.

Messy and rambunctious, but it’s the future!

When Chris Messina announced his new job as Uber’s Developer Experience Lead in January 6, 2016, he wrote:

Uber is not without controversy and misunderstanding. I see this, know this, and am vigilant about this. Facebook too, in its early days, was not without controversy. The future arrives in mysterious and abrupt ways, and in the present, is often a messy and rambunctious guest.

…Uber has certainly had its fair share of criticism in its pursuit of the future, but it is not nostalgic. It is competitive, fierce, and entrepreneurial — and these are traits that I’ve found in most of the companies that I’ve rooted for over the past ten years living and working in Silicon Valley.

I interpreted this as “They have a reputation for being terrible, but the future is made by terrible people! Stock options ahoy!

He announced his resignation exactly a year later with the standard farewell platitudes. A better indication of why he left appears in a tweet he made yesterday in response to Fowler’s article:

Not Cool, Uber: How Uber treats its drivers (and remember, they’re not employees)

Illustration by Susie Cagle. Click to see the source.

It’s not just Uber’s well-compensated techies who are treated badly; the drivers are even worse off, as shown in Dave Craige’s January 2016 article, Not Cool, Uber.

The false promises of big bucks for drivers

It wasn’t that long ago that Uber was telling the Washington Post that an UberX driver working 40 hours a week makes a median wage of almost $75,000 in San Francisco and over $90,000 in New York City. The New York Post followed up with a report that said that once you factor in the 20% “skim” that the company makes, sales tax, a worker’s compensation payment to the Black Car Fund, and expenses, New York UberX drivers make 30 cents an hour more than New York cabbies.

Also in late 2014, BuzzFeed’s Johana Bhuiyan made an arrangement with Uber’s New York General Manager Josh Mohrer to take rides with 11 randomly-selected UberX drivers and review their pay statements. She got 8 pay statements — 2 of the drivers weren’t comfortable disclosing that information, and “Uber did not provide pay statements for the last driver”.

She found that the expenses take a significant slice of an UberX’s driver’s money, and once you factor them in, an UberX driver is making cabbie wages — or less.

To help their driver partners (remember, they’re not employees) get cars, they partnered with a predatory auto title loan company

Until 2015, Uber had a relationship with Santander Bank, who provided financing for “driver-partners” (not employees) who needed cars, but had to drop it when it became clear that Santander was making expensive subprime loans and illegally repossessing cars financed for U.S. Armed Forces veterans.

In late 2015, they partnered with Westlake Financial, whose subsidiary Wilshire Consumer Credit, has a reputation for providing predatory auto title loans and had been hit with $44 million in fines and restitution by the Consumer Financial Protection Bureau for “deceiving consumers by calling under false pretenses, and using phony caller ID information, falsely threatening to refer borrowers for investigation or criminal prosecution, and illegally disclosing information about debts to borrowers’ employers, friends, and family.”

And if you think Uber treats their tech employees and drivers (who aren’t really employees) badly, wait until you see what they’ve done to the competition!

They’re not above sabotaging the competition. In August 2014, Uber gave 177 “brand ambassadors” burner phones and credit cards to order and then cancel rides from their competitor, Lyft. As you might expect, Uber denied that they’d never get involved in these sorts of shenanigans, but leaked documents indicate that Uber was indeed behind the plot.

In a 2014 Vanity Fair profile, Uber CEO Travis Kalanick admitted to playing a tried-and-true trick in order to derail Lyft’s fundraising round:

The most recent target that Kalanick has had in his crosshairs is rival ride-sharing app Lyft, which attaches giant pink mustaches to the grilles of its cars. Kalanick readily admits to trying to tamper with a recent fund-raising round that Lyft was doing.

“We knew that Lyft was going to raise a ton of money,” says Kalanick. “And we are going [to their investors], ‘Just so you know, we’re going to be fund-raising after this, so before you decide whether you want to invest in them, just make sure you know that we are going to be fund-raising immediately after.’ ”

Worth watching: Cracked’s video, Why Uber is a Terrible Company Run by Maniacs

If you haven’t watched it yet, take a 12-minute break and watch this insightful people from the people at Cracked:

Had enough Uber yet?

As superstar programmer David Heinemeier Hansson writes in his blog:

Uber is what you get when you take Silicon Valley’s most toxic values, add billions of dollars in venture capital, and spice it with endless adoration from a fawning tech press. The resulting cocktail has turned as putrid as it’s been potent. And the inebriated corporate culture of Uber is acting as reckless and callous as a dangerous drunk.

If you feel like canceling your Uber account after reading Fowler’s story and all the background info above, the steps for doing so are listed above. As he writes (the emphasis is mine):

So it’s time for the consumers of Uber to do what its board, venture capitalist-backers, and royal protectors never will: Impose real consequences on Uber for its appalling behavior. Because without the approval of riders, Uber is nothing. None of the billions of dollars in funding will do anything to save them, if enough people say “enough” and stop using the app.

I get that may well be a bit inconvenient at times. And alternatives like Lyft may not be squeaky clean either. But these are all excuses for people trying to avoid the bare minimum a consumer can do when faced with a company that repels it: Stop buying. It’s really that simple. You don’t have to print any signs, you don’t even have to go to a rally, just #DeleteUber, and you’ll sleep just that tiny bit better tonight.