01 Jan 2016
2015年对自己来讲是不错的一年。和往常一样,是时候回过头来看看这一年都干了些什么,有哪些地方进步了,还有什么不足,另外就是开始想想新的一年可以有哪些改变。
2015发生了什么
开源社区活动
开源社区的活动,是工作内也是工作外一直在做的事情,因为这些事情和工作(写代码)本身一样,是自己喜欢的事,所以花再久的时间都不会觉得累。
今年做的最主要的事情是深JS-2015 JSConf China和Shanghai JavaScript Meetup,做这些事情的过程当中也在改变着自己。我在之前的文章中也分享过自己的一些感受:
参加的第一个编程竞赛。之前自己没有参与过任何黑客马拉松之类的活动,原因是自己空余的时间很多时候都在编程,不需要一两个晚上不睡觉,待在同一个地方来完成一些编程挑战。Data Canvas项目有点不一样,是由一个国际组织swissnex举办的,在全球范围内选了7个国家的主要城市,每个地方安装DIY开源硬件,收集空气质量数据然后做出数据可视化效果的比赛。
自己参加的原因有很多
- 开源硬件是自己在那个时候比较想尝试的
- 公司刚好也在做数据可视化的项目
- 学习和实践一些技术(Nodejs硬件编程,React + D3 数据可视化)
- 不需要集中在一到两天时间内去完成
- 上海也是项目选中的城市之一,收集自己身边的空气质量数据然后可视化是最有意思的事情
- 可以和全球其他地方的开放数据研究人员同步竞争和交流
最后自己的作品并没有获得什么奖项,只是被选为展出的作品之一。但是结交了很多开放数据工作的团队和朋友,认识到自己思路(过于集中到编程而不是艺术或可视化)上的缺陷,没有组建团队的意识(都是一个人在参与)。
缅甸大选项目是这一年工作的重心,时间持续比较长,工作内容也比较复杂。这也是加入Wiredcraft以来做的最大的一个项目。项目需求也比较特殊
一直以来都因为公司是做的外包项目(拿钱做事,做完走人),所以没有多少成就感。但从技术角度来讲,这个项目不仅让自己接触并掌握了使用Electron开发桌面端应用的能力,更是开拓了自己的专业技能视野(包括使用React Native做移动端开发)。从非技术来讲,自己做的这些工作真正帮助并影响了一个国家的命运,不能不感到自豪。
TLDR: 我离开了工作3年多的上海,辞掉了在Wiredcraft的工作,现在在墨尔本一家叫做Envato的公司工作。
这个应该对自己来讲不仅是今年作出的最大改变,也很有可能是人生当中最大的改变。之前一直有打算和其他人分享这个过程,但是因为各种原因没有做到。
首先,离开Wiredcraft换到另外一个地方工作是完全不在计划范围内的。它起始于今年五一假期来墨尔本的旅行。参加了这边的一期墨尔本JavaScript meetup, 然后听说到Envato这家公司。先前了解到这家公司的其他产品(Themeforest, Tutsplus)但并不知道它们属于同一家公司,网上搜索了相关的信息之后觉得是一家还不错的公司,所以就抱着试一试的心态做了一份简历然后投了出去。后来经过几轮的面试,准备雅思考试,签证,前后大概花了半年的时间,最后拿到了这边的工作签证。
加入Wiredcraft是我一直以来觉得人生的转折点。它在各个方面开拓了我的视野
- Open source and open source community
- Startup culture and management
- Multi country culture and diversity
- Work and life balance
Wiredcraft可能是我目前为止能够在国内找到的最合适的工作地点之一,灵活的工作时间,开放的技术氛围,get sh*t done的精神。和国内大公司的加班,加班,加班形成了鲜明的对比。也发现和认识到了公司在近几年发展过程中的一些问题,以及老板和同事们是如何解决的过程。创业公司在发现和解决问题的这些经验,不能不说是一比宝贵的财富。当然最重要的是有一群可爱的同事。Yuki老师说的对,和前同事一起吐槽新公司是最开心的事情了。
其实没有离开这个地方的理由,只是感觉世界很大,还有很多地方可以去,很多新的东西可以尝试,更多的东西值得自己去学习。至于在大公司和小公司工作的区别和感受,需要再过一段时间才会知道哪个好哪个坏。之后有时间了再写出来吧。
去了哪些地方
回过头来数数今年去过哪些地方,虽然已经过去了但是还是觉得很激动。很难想象我的护照去年这个时间还是白本,到现在竟然也还去了不少地方。
最先是五一假期在澳大利亚墨尔本待了两个星期,之后和Wiredcraft的同事一起游玩了德国柏林和捷克的布拉格。十一又去了斯里兰卡,到现在又回到了墨尔本。中间转机也经过了不少国家,迪拜,马来西亚,泰国,当然还有国内的城市北京,深圳,香港。
旅行可能并没有什么意义,也并不需要什么意义。去到一个新的地方,哦,原来是这样。就够了。
如果你也有出国走走的打算,抽个时间把护照和签证办了吧。没有想象中的那么困难。
2016年可以有哪些改变
一直都没有对下一年有过什么认真的规划,因为觉得自己的新一年充满着无数的变数,根本规划不来。但是可以设定一些小小的目标,即使不完成也没有关系。
去年的这个时候给自己订的目标是出国走走,仅此而已。现在看来还算完成的不错。2015年也走了不少地方了,加上现在的这个状态,好像也没有什么更多特别想去的地方。有机会的话就出去走走,没有的话也没关系。
一个不知道要不要做的改变就是社交了。在国内的时候就不太喜欢人多的地方,也不喜欢在人多的时候主动去找人交流。其实有个词形容自己比较合适Introvert. 在Urban词典里是这样定义的:
Opposite of extrovert. A person who is energized by spending time alone. Often found in their homes, libraries, quiet parks that not many people know about, or other secluded places, introverts like to think and be alone.
差不多就这样了吧。新的环境还好,不难适应。关键是要不要去适应了,哈哈。
10 Nov 2015
Seven months ago, React Native for iOS came out. I built the ShenJS app for the 2015 JSConf China the day after the release. It was more out of curiosity than anything else.
At Wiredcraft, we’ve also been building apps with the Ionic framework (Cordova + Angular.js) for one of our clients, so it’s only fair that I compare these two.
Working with Ionic
- Write once, run everywhere
- Poor performance with complex components (e.g. Google Maps)
- Toyish
- Note: The apps we built were iOS only - I have no idea how it plays on Android.
Mostly, we were kind of bummed by the performance. Before React Native for Android came out a few weeks ago, we had been building web and Windows (desktop) apps (really) with React (and Electron) for the Myanmar elections, so I got excited about giving React another try.
Our team is very comfortable with React + Redux, and I happened to find the soundredux project by Andrew Nguyen. It’s a great app and I’ve been using it instead of the official Soundcloud client for a week. I liked it so much that I wanted to make it work on Android.
So, in an effort to learn ES6 and redux, I started to work on SoundRedux Native, a simple Soundcloud native client.
How
So far I’ve only focused on Android development because I don’t have an iOS device and it’s only fun when your code runs on your own device.
Setup your local dev environment
npm install
- Check Android Setup
react-native run-android
To run it on a real device, bundle the js file into the apk:
- create an assets folder under
android/app/src/main
curl "http://localhost:8081/index.android.bundle?platform=android" -o "android/app/src/main/assets/index.android.bundle"
The code logic
For a typical browser-based web app with React + Redux, you will probably write your root component for your app like this (sample code from sound-redux again!):
// root.js
import React from 'react'
import { Provider } from 'react'
import configureStore from './store/configure-store'
import App from './containers/app'
const store = configureStore()
class Root extends React.Component {
render () {
return (
<Provider store={store}>
{() => <App />}
</Provider>
)
}
}
export default Root
To make it work for React Native, import react-native
instead:
// import react-native
import React from 'react-native'
// same for react-redux
import { Provider } from 'react-redux/native'
The next step is to mount it to the DOM or native view if it’s for mobile:
import ReactDOM from 'react-dom'
import React from 'react'
import Root from './root'
ReactDOM.render(Root, document.getElementById('main'))
And for React Native:
import React from 'react-native'
const {
AppRegistry
} = React
import Root from './root'
AppRegistry.registerComponent('soundreduxNative', () => Root)
That’s all you need to use Redux with React Native. The next step is to build the view part.
With all we’ve done for the data layer, there’s no wonder that Facebook wrote this in their blog “React Native for Android: How we built the first cross-platform React Native app” by the time they opened source react-native for Android.
So instead of introducing explicit if/else checks for the platform, we tried to refactor platform-specific parts of the UI into separate components that would have an Android and iOS implementation. At the time of shipping Ads Manager for Android, that approach yielded around 85 percent reuse of app code.
Here’s a diagram that explains how the whole thing works:
After spending some time on it, I found there was still a lot of work that needed to be done, either by improving my own code or Facebooks’s design on their thread system (here we are talking about the UI thread which has a direct impact on the user experience).
They have a very long section about common sources of performance problems on the performance page which is exactly what I’ve run into with my app.
Since we’re using redux in the app, and for each scene, I have a mapStateToProps
method which connects the needed data to each component container (to avoid having to pass every props from the root component all the way down - this could save a few rerenders because not all components need the whole state tree).
When the user clicks a song from the song list, a few actions will be triggered.
<TouchableOpacity onPress={this.playSong.bind(this, parseInt(rowId))}>
<SongDetail />
</TouchableOpacity>
And in the playSong
function I will need to dispatch
the playSong
action which will change a few states in the redux reducer (with lots of calculation and network request). After that, I will navigator
the screen to the Song
scene which will show the user the detail view of the current playing song.
playSong(i) {
const {playlist, dispatch, navigator} = this.props
dispatch(playSong(playlist, i))
navigator.push({
component: SongContainer,
name: 'Song'
})
}
If I leave it like this without doing anything, when the user touches the screen to play a song, the TouchableOpacity
component will have a noticeable lag and the opacity effect won’t kick in until the dispatch
operation is finished. In other words “Dropping JS thread FPS because of doing a lot of work on the JavaScript thread at the same time”.
So what I can do here is use the InteractionManager
to postpone a few actions and let the animation finish first and then do the other operation, so the user won’t feel the lag from the UI.
playSong(i) {
const {playlist, dispatch, navigator} = this.props
// use this to leave room for animation
InteractionManager.runAfterInteractions(() => {
dispatch(playSong(playlist, i))
navigator.push({
component: SongContainer,
name: 'Song'
})
})
}
This is what it does from the documentaion:
InteractionManager allows long-running work to be scheduled after any interactions/animations have completed. In particular, this allows JavaScript animations to run smoothly.
Apart from that, this React specific performance trick also works:
shouldComponentUpdate(nextProps, nextState) {
const shouldUpdate =
!shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState)
return shouldUpdate
}
What I’ve achieved so far
- Reuse the data fetching logic from soundredux and store it with redux
- Infinite scroll with react-native
ListView
component
- Player component with the help from @xeodou’s react-native-player module
- Search songs in a separate search scene
TODO:
- Clean up code
- Improve performance
- Make it work on iOS
- Add user login logic, and potentially try out
DrawerLayoutAndroid
component
- Publish to Google Play or even App store
What I’ve learned
The good and the bad
The bad first, and then the good will make it right :D
Bad:
- Ecosystem: There’s not enough modules (yet), especially for Android.
- Documentation: Still needs to improve,
UIExplorer
is a good place to start.
- Performance: Animations and slow navigator transitions as mentioned in the performance page on the official documentation website
Note: I’m pretty sure the performance is something that could be fixed by improving my own code, and this is part of the reason that I’m sharing this with you. If you have any experience or suggestion, I’m all ears. Either a pull request or a new issue in the repo would be great!
Good:
- Good community and ecosystem. No wonder the React communutiy is one of the best and active open source communities.
- Native UI component! Good performance!
- Learn once, write everywhere! If you know React, you already know how to write React Native apps.
- Code reuse, from the Web. If your project happens to use React for the web part, you can reuse lots of code for your mobile client.
- Wrapping native modules is easy, so when there’s not enough modules or you have a performance issue, you can write your own and contribute to the ecosystem.
PS: As a JavaScript developer without any Java programming experience, I made this Couchbase-Lite binding for react-native at weekend react-native-couchbase-lite. It’s fun!
Conclusion
As you are reading this post, there’s a chance you may be wondering whether or not react-native is right for your next production project. This is not the purpose of this post and I do not have a clear answer. I’ve been enjoying the process and learning React Native helps me in many ways. I suggest that you check out what other people are doing:
Original post on Wiredcraft Blog BUILDING A NATIVE SOUNDCLOUD ANDROID APP WITH REACT NATIVE AND REDUX
18 Sep 2015
Steps for the translation workflow in a Electron application:
Requirements
- jsxgettext - this is a node module to generate po file from JavaScript file
npm install jsxgettext --save-dev
to install
- gettext - native gettext tool to merge po files
brew install gettext
and run brew link gettext --force
if needed.
- node-gettext - this is a node module to parse and read PO file
npm install node-gettext --save
to install
Prepare the folder structure
Create a locale
directory to put the origianl po files
.
├── en.new.po
├── en.po
├── my.new.po
└── my.po
Prepare a Makefile
and add the genPo
task
bundle.js
is the JavaScript file which will use to generate the new PO file
NODE_BIN = node_modules/.bin
genPo:
$(NODE_BIN)/jsxgettext -j dist/bundle.js > locale/my.new.po
$(NODE_BIN)/jsxgettext -j dist/bundle.js > locale/en.new.po
msgmerge locale/my.po locale/my.new.po -o locale/my.po
msgmerge locale/en.po locale/en.new.po -o locale/en.po
.PHONY: genPo
Run make genPo
to generate and merge the po files
Use it in the client
First you need to set up gettext
/**
* Getttext
*/
var gettext = require('node-gettext')
var fs = require('fs')
gettext = new gettext()
// add the language you need, here `my` means Myanmar
gettext.addTextdomain('en', fs.readFileSync(path.join(__dirname, './locale/en.po')))
gettext.addTextdomain('my', fs.readFileSync(path.join(__dirname, 'locale/my.po')))
// set the default domain
gettext.textdomain('my')
And finally you can use it in you HTML or whatever template language you have, here I use JSX with React. In the render
function of a component
render () {
<span>{GT.gettext('Some really weird language here...')}</span>
}