使用 React Native 開發 Android 的心得

實際用於 MOPCON 2018 Android App

很榮幸這次 ReactMaker 可以參加 MOPCON 的 Android APP 製作,我們這次挑戰使用 React Native (RN) 來開發 MOPCON 的 APP , RN 是 Facebook 開發的框架,可以讓你使用 Javascript 來製作 APP , 它與 phonegap/cordova 不一樣, RN 編譯完以後產生出來的是原生檔案,而不是用 webview 包裝起來的 app,在這次開發過程中,我們玩過了很多套件也更了解了 RN ,所以就想分享給大家,讓想接觸 RN 的人可以更方便入門。

殊不知困難的不是程式,而是…沒有 Android 手機阿

為什麼想要使用 React Native

我們都是前端,過去以往的合作經驗都是寫 React 框架的網站開發, 但是想挑戰製作 APP 的實戰經驗,體驗看看 React Native 是不是真的像傳聞中的那麼方便!

MOPCON APP 起跑

基本上,3月到4月之間會有一個見面會吧,組長介紹一下目前的成員,介紹一下今年 APP 會有的功能以及 UX,之後我們就是在 slack / zoom 遠端討論或是開會。

分配工作

就跟做其他專案一樣,第一步就是 review 全部的 UI/UX 圖,找出比較難的部份,例如頁面切換,或是多語系等等,兩人分別去研究,再找一個時間把研究結果分享給對方。
接著以功能面來分配,同時在 github 上面開 issue 追蹤,像是海門就負責的議程相關的頁面,Andy是負責小遊戲相關的功能,做完頁面之後自己去 issue 接下一個想做的頁面,在製作過程中如果有共用的元件就會抽到 components 給其他人共用。

怎麼制定 RN 專案架構

其實我們是根據之前 web 專案的架構來區分,統一把 api 的部份做成一個共用的函式庫,避免每個檔案都自己去呼叫 api ,共用的元件放在 components 裏面,每個頁面的檔案放在 containers 內,另外切出多國語係的資料夾來放置英文中文兩種 json, 整個 app 的共用顏色等等資訊放在 theme 裡面。

怎麼取得螢幕寬高呢?

styled-component中常常會需要指定寬度,每隻手機的寬度或是高度也不一樣,所以我們就可以使用 RN 內建的 Dimensions 來取得裝置寬高。

import { Dimensions } from 'react-native';
// style-component
const width = (Dimensions.get('window').width - (16 * 3)) / 2;
export const CardSmall = styled.TouchableOpacity`
width: ${width};
height: ${width};
`;

怎麼做背景圖片呢?

  • RN要加背景圖片不能直接在css中指定,這點和web開發不同
  • 可使用image並搭配position: absolute將圖片固定位置
const Background = styled.Image`
flex: 1;
position: absolute;
top: 0;
left: 0;
`;
// 主要的頁面Component放<Background />下面
<View>
<Background
source={image_url}
/>
...
</View>

怎麼把資料儲存在手機上面呢?

  • AsyncStorage 是個類似瀏覽器 localstorage 的功能,可以將資訊存放在 app 中,直到移除 app 才會消失
import { AsyncStorage } from 'react-native';
await AsyncStorage.setItem('speaker', JSON.stringify(speaker));
const speakerText = await AsyncStorage.getItem('speaker');

不同頁面之間怎麼保持暫存資料

可能大家會想到使用 Redux 或是 Mobx 的功能,我們這次是使用 React 的 Context API 來當作暫存記憶體,他可以讓我們在不同頁面之間透過 Props 傳遞資料

// Provider
export default class Provider extends Component {
...
state = {
missionStore: {
balance: 0,
setBalance: this.setBalance,
},
}
...
}
// Consumer
export const Consumer = (...stores) => (ComposedComponent) => {
...
render() {
return (
<myContext.Consumer>
{
context => <ComposedComponent context={context} />
}
</myContext.Consumer>
);
}
...
}

// Usage
@Consumer('missionStore')
export default class ModalExchange extends React.PureComponent {
...
const {balance, setBalance} = this.props.context.missionStore;
...
}

怎麼排版?怎麼製作CSS

RN 跟 Web 開發有一個很大的不同點, web 可以使用 css/sass/less 來做開發,可是 RN 只能寫 inline style,這是比較不方便的地方,畢竟我們都想要用一個 className 來讓畫面上的元件乖乖聽話,我們後來使用 styled-component 來幫忙解決這個問題,styled-component可以讓我們用類似寫 css 的方式來產生出 inline style,還可以透過 props 來改變長度或是顏色等等。

RN裡面所有的元件預設都是 display: flex,所以我們建議你,可以先把 flexbox 學好,再來開發 RN 的時候會降低很多排版上遇到的問題。

import * as Style from './style';
import Quiz from './Quiz';
<Style.Container>   <-- styled-components
<Quiz /> <-- component
</Style.Container>

RN 怎麼做到掃描 QR Code 這種功能?

基本上這就要靠套件的威力了,有一些善心人士會製作套件來讓我們達到原生 APP 才有的功能。

  • 大地遊戲需要掃描QRCode,因此選擇此套件來使用
  • 就是在這裡遇到最大的難題…沒有Android手機,幸好組長有提供給我們開發用
import QRCodeScanner from 'react-native-qrcode-scanner';
onSuccess = async (e) => {
const data = e.data;
console.log('data', data);
}
<QRCodeScanner
cameraStyle={{height: cameraHeight}}
onRead={this.onSuccess}
topContent={null}
/>

react-native-camera

  • 搭配react-native-qrcode-scanner使用
  • 這次開發到一半時有遇到套件相容性問題,因此指定版本安裝
"react-native-camera": "git+https://git@github.com/react-native-community/react-native-camera"

怎麼製作多國語系?

這個也是依賴套件來製作的,我們會定義中文跟英文的 js 檔案,放在 local 資料夾內,方便維護。

react-native-i18n

  • 可以階層式定義多語系
// zh.js
export default {
home: {
schedule: '議程',
}
}
import I18n from 'react-native-i18n';
I18n.t('home.schedule')

首頁的圖片輪播怎麼做?

這也是透過套件來製作的,但是 MOPCON 需要的樣式和套件預設差很多,我們調整很久才完成樣式設定。
react-native-snap-carousel

renderItem = ({ item, index }) => {
return (
<TouchableOpacity onPress={this.openLink(item.link)}>
<Style.CarouselItem width={width} source={{ uri: item.banner }} />
</TouchableOpacity>
);
}
<Carousel
inactiveSlideScale={0.94}
sliderWidth={width}
itemWidth={width - 40}
data={carousel}
renderItem={this.renderItem}
/>

啟動畫面怎麼做?

啟動畫面比較難一點點,除了要安裝套件以外,需要設定很多Android設定檔才能完成。
react-native-splash-screen

畫面載入後呼叫 SplashScreen.hide() 關閉splash screen

import SplashScreen from 'react-native-splash-screen';
componentDidMount() {
SplashScreen.hide();
}

怎麼使用 SVG 的 ICON 在 APP 內?

react-native-vector-icons

  • 需要設定很多Android設定檔才能完成,按照ReadME步驟操作
  • 為了使用svg icon

怎麼切換頁面?

react-navigation

  • Mopcon navigation有兩種樣式,因此我們實作兩種樣式的header
  • 設定 options 的 headerLeft 及 headerRight
  • 包裝成 NavigationOptions.js 供各頁面呼叫
  • 提供 mode1 及 mode2 兩種樣式
static navigationOptions = ({ navigation }) => NavigationOptions(navigation, '頁面Title1', 'mode1')
static navigationOptions = ({ navigation }) => NavigationOptions(navigation, '頁面Title2', 'mode2')

怎麼做推播?

我們這次有做推播的功能,推播主要是透過 firebase 來推送,所以必須要安裝 react-native-firebase 來與 FCM 串接。安裝之後還必須到 firebase 官網設定,下載一個 google-services.json 到你的專案資料夾內。

遇到的一個小地雷!!

我們在某個 API 需要用到 search params 功能,原本我們都是使用瀏覽器的內建功能,在本機跑也正常,可是發佈正式版的時候就跳錯誤了,原來是開發的時候模擬器底層有透過瀏覽器補上功能,可是實際上手機沒有這個功能,所以就另外安裝套件了解決了。

url-search-params

  • 解析網址參數用
  • 開發時使用模擬器搭配chrome debugger,因為是開啟chrome所以沒有問題
  • 但是在實機上使用並無此套件會發生錯誤,因此要安裝 url-search-params
npm install --save url-search-params

試調/偵錯

其實直到專案結束為止,我們兩人都覺得不是很好去做 debug ,很多 RN 畫面上的錯誤,通常出現的紅字沒有辦法很確切的告訴你真正的錯誤在哪,我會推荐使用 react-native-debugger 會比較好去 debug。

這套工具除了可以讓你看看 console 之外,還有查看 virtual dom 的功能,身為前端工程師,我們已經很習慣使用 React Dev Tool 來察看我們的 virtual dom 上面所有元件的 state/props , 現在剛踏入 RN 的世界中,當然我們也需要這個功能,幸好套件提供了…

API 如何 debug 方面,由於 RN 是透過 webworker 來發送 XMLHttpRequest 所以基本上沒有辦法看到 Network 狀態,所幸後來還是在網路上找到方法把 fetch 轉到 originalXMLHttpRequest 才能看到以及偵錯

如果你是使用 chrome debug 呼叫 api 會有 cross-domain 的錯誤,所以你需要安裝 chrome 的 extension 去允許跨域的動作。

如何在 Github 上面進行開發

起初還不穩定的時候我們都在 master 上面開發,但是兩人之間都會使用 pull request 來告訴對方自己改了哪些程式碼,在 app 穩定之後我們就使用類似 git-flow 的開發方式,差別在縮減了一些分支上的規定,但是一樣保留有 master, develop, feature/XXX 三種分支來開發。

在 github 上面開發的專案由於是公開專案,所以就會把一些重要的資訊抽出來變成環境變數,像是 api 的 url 等等資訊。

我們主要透過 issue 追蹤目前的 bug 或是還沒有完成的功能,以及使用 github-project 把 issue 放進來審視完成的進度。

CI/CD

承接上面的議題,由於我們使用類似 git flow 的方式來開發,所以最後到 master 分支上面的版本會希望有持續整合的功能來協助我們自動把原始碼打包成 apk,其實 github 上面有許多方便的 ci/cd 工具任君挑選,我們是選用 travis 來幫忙。

基本上我認為所有的工具都可以達到相同的功能,只是你有沒有熟悉而已,在這個專案中 travis 真的幫助到我們很多,尤其是打完 tag 以後會自動把 apk 推送到 github-release 頁面上讓其他成員下載。

完成之後有什麼感想?

對於前端工程師來說真的是最快速開發 App 的一條路,但是和開發web不同的是畫面不易調整及需要修改到原生 Android 或 ios 的設定,雖然大部分套件的ReadMe都有寫,不過開發中有遇到一些套件未更新 README ,設定上有遇到問題。

不過之後如果有需要開發App的話,我覺得 RN 真的是一個不錯的選擇,可以說如果你會 ReactJS + CSS 就可以開發 RN 了,就像我們使用 Context 及styled-component,這些原本開發web的技術及套件來使用。

很可惜的就是本來有在討論要使用區塊鏈技術來發行 mo 幣,後來時辰太趕了,所以就只好把小遊戲做在本地端。

最後的最後,強烈建議電腦的記憶體要 16G 啊…
開模擬器真的很吃資源,Andy原本使用 8G 的筆電進行開發,真的是很Lag…然後他就換了一台 Macbook Pro !!

以上就是我們ReactMaker這次開發 Mopcon Android App 的心得,如果有想問的或者是建議也歡迎到粉絲團跟我們聊聊喔。