29 Aug 2016

Yes, I’m talking about a real dark theme for Slack on OSX. The only catch, you have to build it yourself. It’s super easy!
Just go clone my repo here, and follow the instructions in the README. Basically it’s as easy as this:
npm install -g nativefier
nativefier https://[your-org].slack.com --inject darkslack.css --name "Dark Slack" --icon slack.icns
This generates an Electron app that wraps the web version of Slack. Since we are wrapping the web version, you do lose the organization switcher on the left. But, you can still sign in to multiple organizations and switch between them with command-K.
Honestly, I’m kind of enjoying the noise reduction that comes with only having one organization open at a time. Oh, and also my eyes aren’t bleeding.
18 Jul 2016
Update 7/21/2016: I have changed the links for the sites out from Galaxy to Heroku. All the Galaxy information still applies, just using Heroku since it’s free.
Last week, I gave a talk at Crater Remote Conference about how we build, deploy, and distribute Meteor Cordova apps automatically. The video isn’t available yet, but the slides are available here. And so you don’t have to flip through all that, here is the gist of the presentation.
tl;dr
What if I told you that you could build, deploy, and distribute an iOS app built with Meteor like this? launch is here.
launch build myapp.com
launch galaxy myapp.com
launch testflight

Back Story
We are currently developing an app for iOS and Android. We chose Meteor so that we could target both platforms using the same code base (thanks to the Meteor build tool and Cordova). The development experience has been pretty great, but we made the decision early on that we wanted to be able to ship 1 alpha build a week. This would allow us to show progress to our stakeholders, and get the app in the hands of our team often.
Current Build Process

Our current build process is pretty involved and tedious to do manually. It takes about 30 minutes if you have everything set up, have the process memorized, and are all around having a good day. Once a week, 30 minutes isn’t that hard to sacrifice. But on a bad day, this can take forever. It’s also not very conducive to the idea that everyone on the team can deploy.
There Is A Better Way
We developed a tool called launch
that automates this process for you! You can run it locally, or on a CI. This way, we could focus on writing code, and deploy based on pull requests or release tags.
:allthethings: launch
Can Do
- build iOS app
- build Android app
- deploy to Galaxy
- deploy to Hockey (both iOS and Android)
- deploy to iTunes Store/TestFlight
- deploy to Google Play
Getting Started
npm install -g meteor-launch
launch init # adds launch.json
vim launch.json # fill out the vars
That’s all you have to do to add launch
to your new or existing Meteor project. There is documentation on all the actions supported, and explanations on how to get the variables you will need.
Examples
We have set up a couple examples of how to use launch
, including how to run them on Travis.
Launch Basic Example
This project is the boilerplate you get from a meteor create
, and demonstrates how to build an Android app, deploy it to Galaxy, and upload the app to Hockey for distribution using these commands:
launch build https://launch-basic-example.meteorapp.com
launch galaxy https://launch-basic-example.meteorapp.com
launch hockey
Launch Todos Example
We also have the Meteor Todos example, which demonstrates how to use all the launch
actions:
launch build https://launch-todos-example.meteorapp.com
launch galaxy https://launch-todos-example.meteorapp.com
launch hockey # uploads iOS and Android
launch testflight
launch playstore
TestFlight and Google Play Store uploads you will have to take my word for, but try it out and see for yourself!
How launch
Helped Us
Our original goal was to ship 1 alpha build a week during development of our app. With launch
: we have been shipping multiple builds a day. It’s now easy for anyone on our team to ship a build to our alpha and beta environments just by creating a GitHub release tag. Our stakeholders are well informed about progress (and they are happy). Selfishly, I am super happy because building and deploying is the worst.
Shipping 2 builds a day manually would take over 5 hours a week. It takes a fraction of that time to set up launch
, and then you never have to think about it again.
Check It Out
The presentation and the docs go in to a lot more detail about how to set everything up, and how to get running on a CI.
Let me know if you have any questions! I am happy to help out.
18 Apr 2016
Well… it was an afternoon recently. But, you get the idea.

I have built apps using Objective C, and I have been building a Cordova-based app using React and Meteor. So, I have at least an idea of what’s going on in both situations. I have always dreaded jumping in to Objective C, and I have always been let down by the feeling of a Web View app. Which is why React Native has been on my radar for a bit.
React Native promises a lot
- Write javascript
- Generate native code in iOS and Android
- Share code between the two
I am always skeptical of frameworks that promise you can live the dream. They always seem to fall short, or are buggy, or etc. This is probably why I waited so long to try out React Native, but I kept hearing so much about it.
The App
I wanted to create a simple app that checks the status of my bitcoin miner on my mining pool, Bitminter. Here is the basic flow:
- Take a Bitminter API key
- Make a request to the Bitminter API
- Display the data received
It’s Awesome
I’ve been using React a lot, so when I generated a React Native project, it just felt right. Here are some snippets.
I was able to utilize React’s state for storing the API key and the returned data.
class BitminterNative extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
api_key: null,
input_value: null,
};
}
}
React’s lifecycle functions (componentDidMount) allowed me to easily initiate the request to the API.
componentDidMount() {
this.loadFromStorage()
.then(() => {
if (this.state.api_key) {
this.fetchData();
}})
.done();
}
fetch
is available so the request was easy.
getDataOptions = () => {
return {
headers: {
'User-Agent': 'app',
'Authorization': `key=${this.state.api_key}`,
},
timeout: 5000
};
}
fetchData = () => {
this.setState({
user: null
});
fetch(REQUEST_URL, this.getDataOptions())
.then((response) => response.json())
.then((responseData) => {
this.setState({
user: responseData
})
})
.catch((error) => {
console.log("Error in fetch");
})
.done();
}
There is a polyfill for CSS flexbox properties, so styling was super easy.
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#002b36',
},
});
render() {
<View style={styles.container}>
// etc
</View>
}
Native Mappings
React Native has a bunch of mappings to native functionality out of the box (camera, photos, storage, etc). There is also a really great ecosystem of React Native packages.
I wanted to be able to store the Bitminter API locally so you don’t have to put it in every time (very tedious). React Native handles this out of the box with AsyncStorage
. This lets you read and write to native storage writing only javascript. I just defined methods that attempted to load the key from storage. If it does load, it updates the React state. If it doesn’t load, it presents an input which takes the key and then writes it to storage on submit.
async loadFromStorage() {
try {
const value = await AsyncStorage.getItem(STORAGE_KEY);
if (value !== null) {
this.setState({
api_key: value
});
}
} catch (error) {
console.log(`AsyncStorage error: ${error.mesage}`);
}
}
async writeToStorage() {
this.setState({
api_key: this.state.input_value
});
this.fetchData();
try {
await AsyncStorage.setItem(STORAGE_KEY, this.state.input_value);
} catch (error) {
console.log(`AsyncStorage error: ${error.message}`);
}
}
That’s It
The full code is available here.
And, the app is available in the App Store.
It really only took me two hours to make this. It’s not that complicated, and that is thankfully reflected in how simple it was to make.

16 Jan 2016
…or, how we made all our content related classes “likeable” with one file.
Background
I am currently working on a Meteor app that uses Webpack and React. The app contains multiple feeds that contain multiple different types of content (articles, stories, videos, albums, etc.). In order to properly display the unique characteristics of each type of content, the app is currently making use of about 6 different React classes for displaying individual pieces of content.
However, each of these 6 classes are not completely unique. We make use of helper classes to try to reduce the amount of duplicate code. This has been great for the different aspects of displaying content, but is not as effective when classes need to share functionality. That’s where mixins come in.
React Mixins with ES6 Classes
The original React.createClass
syntax made including mixins very easy:
const Article = React.createClass({
mixins: [Likeable],
// everything else
});
But, React is moving towards the ES6 Class syntax for creating classes:
class Article extends React.Component {
// everything else
}
Unfortunately, this newer syntax does not include a way for including mixins. So, we are using the react-mixin module, along with ES7 decorators.
Now, including our mixin looks something like this:
import { Component } from "react"
import ReactMixin from "react-mixin"
import { Likeable } from "app/client/mixins"
@ReactMixin.decorate(Likeable)
export default class Article extends Component {
// everything else
}
The decorate function magically includes our mixin, and all of its functionality in to our Article class. We can do the same thing in all of our other content related classes.
Likeable
Now, on to the functionality. I wanted the ability to make all of our different content classes “likeable” without the need to duplicate that code in all 6 classes, and now we have the setup for that. So, lets create our mixin:
import { Likes } from "apollos/core/lib/collections"
import { nav as navActions, liked as likedActions } from "apollos/core/client/actions"
import Helpers from "app/client/helpers"
const Likeable = {
componentWillMount: function() {
this.likeableAction = this.onClickAction.bind(this)
},
onClickAction: function() {
const entry = this.getEntry();
this.updateRedux(entry);
this.updateDatabase(entry);
return {
type: "FALSY",
payload: {}
}
},
getEntry: function() {
const data = this.data;
if (data.devotion) return data.devotion
if (data.article) return data.article
if (data.story) return data.story
if (data.currentSermon) return data.currentSermon
if (data.series) return data.series
if (data.album) return data.album
},
updateRedux: function(entry) {
this.props.dispatch(likedActions.toggle({
entryId: entry.entryId
}));
},
updateDatabase: function(entry) {
// find existing like
const foundLike = Likes.findOne({
userId: Meteor.user()._id,
entryId: entry.entryId
});
// update database
if (foundLike) {
Likes.remove(foundLike._id);
} else {
Likes.insert({
userId: Meteor.user()._id,
entryId: entry.entryId,
title: entry.title,
image: entry.channelName === "sermons" ?
Helpers.backgrounds.image(this.data.series) :
Helpers.backgrounds.image(entry),
link: Helpers.content.links(entry),
icon: Helpers.categories.icon(entry),
category: Helpers.categories.name(entry),
date: entry.meta.date,
status: entry.status,
dateLiked: new Date()
});
}
}
}
export default Likeable
This looks a lot like a normal React Class, and can use many of the same lifecycle functions that a React Class can (componentWillMount
, componentWillUnmount
, etc). Lets walk through this.
First, we import anything we need for our mixin. In our case, we are including the Likes collection so that we can store it in the database. We are also including some redux actions so that we can update the state of the app. Finally, we include our Helpers to massage our content.
The componentWillMount
function will be called just like a normal React Class, except that it is called prior to the class which is including this mixin. Here, we set our action to take upon the user “liking” something, and bind this
so we can use it throughout our mixin. this.likeableAction
is used in the parent class.
The onClickAction
first handles getting our entry, in this case an article, but as you can see in getEntry
this could be any number of different content types. Then, it updates our redux store to toggle the “likeness” of the content. Finally, it updates the database, either adding or removing the “Like”, respectively.
Conclusion
I was able to drop this mixin in to all 6 of our content related classes, and all of our content became “likeable”. This is a huge win for maintainability, and adding future “likeable” content will be trivial and clean. I plan on refactoring some other parts of our app to use this same approach (infinite scrolling, sharing, etc).
Have you a mixin.
03 Jan 2016
CircleCI has been rolling out a new OSX environment for use in the automated building, testing, and deploying of iOS apps. The team I work with is currently building a Meteor app that we are going to deploy to iOS, Android, and other places, and we have been using CircleCI for a while, so we were excited at the chance to automate iOS builds to staging and production.
We had been running Gagarin tests on the CircleCI Linux containers without issue, but when switching to the OSX containers there were a few gotchas. I didn’t find many others trying to do the same, so I thought I’d share.
circle.yml
An example project with example tests is available here, but here is the meat of it:
machine:
pre:
- brew update; brew cleanup; brew cask cleanup
- brew uninstall --force brew-cask; brew update
- brew install brew-cask
- brew install homebrew/versions/node010
- curl https://install.meteor.com | sh
- brew cask install google-chrome
- brew install chromedriver
dependencies:
pre:
- npm install -g gagarin
- chromedriver --port=9515:
background: true
test:
override:
- gagarin -v -t 10000
gotchas
You can stop reading now, but if you don’t then here is an explanation of the above yaml:
brew and brew-cask and pre-installed on the OSX containers, but brew always needs to be updated, and the version of brew-cask preinstalled is out of date. So, we update, clean, uninstall, and reinstall.
brew update; brew cleanup; brew cask cleanup
brew uninstall --force brew-cask; brew update
brew install brew-cask
node is not pre-installed, so we install the version of node that Meteor likes, 0.10.41.
brew install homebrew/versions/node010
Install Meteor, obviously.
curl https://install.meteor.com | sh
Then, the one that stumped me for a while. chromedriver is pre-installed on the linux containers, but it’s not enough to just install chromedriver and run it. You actually need to install Google Chrome, as well (duh) (which is why we updated brew-cask).
brew cask install google-chrome
brew install chromedriver
Now you should be good to run your tests. Have fun!