로메오의 블로그

[React Native] Webview와 interface 하기 본문

App & OS/Hybrid

[React Native] Webview와 interface 하기

romeoh 2019. 7. 12. 06:55
반응형

Firebase 목록

 

react native에서 webview를 만들고 javascript와 react native간 interface를 해보겠습니다.

web소스는 firebase hosting을 이용합니다.

[FIREBASE] 호스팅 생성하고 배포하기

위 포스트에서 firebase hosting으로 hello world를 찍고 오시기 바랍니다.

프로젝트 생성

$ cd /my/project/path
$ react-native init secondProject --version react-native@0.59.8
$ cd secondProject
$ code .

secondProject 프로젝트를 생성합니다.

react-native 버전은 0.59.8로 설치합니다.

(최신버전인 0.60을 설치했는데, android에서 webview가 제대로 작동 안하네요. -_-

니네 테스트 제대로 안하고 출시하냐?)

 

VSCode로 프로젝트를 열어서 터미널 화면을 두개로 분할합니다.

한쪽 터미널에는 react-native start 명령을 하시고

다른 한쪽은 react-native run-ios 명령을 합니다.

$ react-native start --reset-cache
$ react-native run-ios

보이는 메인화면은 react-native 0.60 메인입니다. (0.59.8은 다르게 생겼어요.)

webview 설치

$ npm install react-native-webview
$ react-native link react-native-webview
$ cd ios
$ pod install

일반적으로 link만 잡아주면 plugin 설치에 문제없는데, react-native 0.6부터는 webview를 사용하기 위해서 pod install을 해줘야 하네요.

 

App.js

import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import { WebView } from 'react-native-webview'

const domain = 'https://firstproject-a04bf.firebaseapp.com/'
export default class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            indexPage: { uri: domain + '?ver=1' }
        };
    }

    render() {
        return (
            <View style={styles.container}>
                <WebView
                    style={styles.webview}
                    source={this.state.indexPage}
                    originWhitelist={['*']}
                    ref={webview => this.appWebview = webview}
                    javaScriptEnabled={true}
                    useWebKit={true}
                />
            </View>
        );
    }
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        paddingTop: 30
    },
    webview: {
        flex: 1
    }
});

 

기존코드를 다 지우고 위와 같이 코딩 합니다.

$ react-native run-ios

webview에 firebase의 firstProject가 표시됩니다.

반응형

화면 수정하기

화면에서 버튼을 누르면 네이티브에서 fetch 통신을 해서 결과를 다시 화면에 넘겨주는 로직을 구현합니다.

firebase의 firstProject에서 코딩하겠습니다.

 

$ pwd
/my/project/path/firstProject
$ touch public/index.js
$ touch public/webViewBridge.js
$ touch public/movie.json

public/index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Welcome to Firebase Hosting</title>

    <!-- update the version number as needed -->
    <script defer src="/__/firebase/6.2.4/firebase-app.js"></script>
    <!-- include only the Firebase features as you need -->
    <script defer src="/__/firebase/6.2.4/firebase-auth.js"></script>
    <script defer src="/__/firebase/6.2.4/firebase-database.js"></script>
    <script defer src="/__/firebase/6.2.4/firebase-messaging.js"></script>
    <script defer src="/__/firebase/6.2.4/firebase-storage.js"></script>
    <!-- initialize the SDK after all desired features are loaded -->
    <script defer src="/__/firebase/init.js"></script>
</head>

<body>
    <h1>Hello world</h1>
    <button id="btnSend">통신하기</button>
    <p id="result"></p>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="webViewBridge.js?ver=4"></script>
    <script src="index.js?ver=4"></script>
</body>
</html>

public/index.js

(function () {

    // react-native에 통신을 요청한다.
    $('#btnSend').on('click', function() {
        var option = {
            url: '/movie.json'
        }

        // "getMovieList"는 react-native에서 받는 메서드 이름입니다.
        window.webViewBridge.send('getMovieList', option, function(res) {
            $('#result').html(JSON.stringify(res))
        }, function(err) {
            console.error(err)
        })
    })

}());

public/webViewBridge.js

(function () {

    var promiseChain = Promise.resolve();
    var callbacks = {};
    var init = function () {
        // 유니크한 아이디를 생성한다.
        // native에서 callback 받을때 id의 callback을 호출한다.
        const guid = function () {
            function s4() {
                return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
            }
            return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4();
        }

        /**
         * javascript => react-native
         * javascript에서 react-native에 메세지를 보낸다.
         */
        window.webViewBridge = {
            send: function (targetFunc, data, success, error) {

                var msgObj = {
                    targetFunc: targetFunc,
                    data: data || {}
                };

                if (success || error) {
                    msgObj.msgId = guid();
                }

                var msg = JSON.stringify(msgObj);

                promiseChain = promiseChain.then(function () {
                    return new Promise(function (resolve, reject) {
                        console.log("react native에 메세지를 보냄 " + msgObj.targetFunc);

                        if (msgObj.msgId) {
                            callbacks[msgObj.msgId] = {
                                onsuccess: success,
                                onerror: error
                            };
                        }
                        window.ReactNativeWebView.postMessage(msg);
                        resolve();
                    })
                }).catch(function (e) {
                    console.error('메세지 실패 ' + e.message);
                });
            },
        };

        /**
         * react-native => javascript
         * react native에서 화면에 결과를 넘겨준다.
         */
        window.document.addEventListener('message', function (e) {
            console.log("react native에서 메세지를 받음", JSON.parse(e.data));

            var message;
            try {
                message = JSON.parse(e.data)
            }
            catch (err) {
                console.error("메세지를 파싱할수 없음 " + err);
                return;
            }

            // callback을 트리거한다.
            if (callbacks[message.msgId]) {
                if (message.isSuccessfull) {
                    callbacks[message.msgId].onsuccess.call(null, message);
                } else {
                    callbacks[message.msgId].onerror.call(null, message);
                }
                delete callbacks[message.msgId];
            }
        });
    }
    init();
}());

public/movie.json

{
    "title": "The Basics - Networking",
    "description": "Your app fetched this from a remote endpoint!",
    "movies": [
      { "id": "1", "title": "Star Wars", "releaseYear": "1977" },
      { "id": "2", "title": "Back to the Future", "releaseYear": "1985" },
      { "id": "3", "title": "The Matrix", "releaseYear": "1999" },
      { "id": "4", "title": "Inception", "releaseYear": "2010" },
      { "id": "5", "title": "Interstellar", "releaseYear": "2014" }
    ]
  }

아름다운 화면이 완성되었습니다.

firebase에 배포합니다.

firebase deploy

$ firebase deploy

 

react native에서 interface 코딩

react native에서 새로고침 하면 firebase 화면이 바뀝니다.

캐시때문에 변경이 안되면 url에 parameter를 주면 cache가 변경됩니다.

constructor(props) {
    super(props);
    this.state = {
        indexPage: { uri: domain + '?ver=2' }
    };
}

webview에 onMessage 속성을 추가합니다

<WebView
    ....
    onMessage={this.onWebViewMessage}
    ....
/>

onWebViewMessage 메서드를 만듭니다.

import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import { WebView } from 'react-native-webview'

export default class App extends Component {
    ....

    /**
     * 화면에서 post를 던지면 react-native에서 받음
     */
    onWebViewMessage = event => {
        console.log('onWebViewMessage', JSON.parse(event.nativeEvent.data))
        let msgData;
        try {
            msgData = JSON.parse(event.nativeEvent.data) || {}
        } catch (error) {
            console.error(error)
            return
        }
        this[msgData.targetFunc].apply(this, [msgData]);
    }

    /**
     * 영화목록을 받아와서 화면에 전달한다.
     */
    getMovieList = msgData => {
        const option = {
            method: 'GET',
            timeout: 60000
        }
        let url = domain + msgData.data.url

        fetch(url, option)
            .then(res => {
                return res.json()
            })
            .then(response => {
                console.log('<====== response', response)
                msgData.isSuccessfull = true
                msgData.data = response
                this.appWebview.postMessage(JSON.stringify(msgData), '*');
            })
            .catch(error => {
                console.log(error)
            })
    }

    ....
};

전체코드는 아래와 같습니다.

import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import { WebView } from 'react-native-webview'

const domain = 'https://firstproject-a04bf.firebaseapp.com/'
export default class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            indexPage: { uri: domain + '?ver=3' }
        };
    }

    /**
     * 화면에서 post를 던지면 react-native에서 받음
     */
    onWebViewMessage = event => {
        console.log('onWebViewMessage', JSON.parse(event.nativeEvent.data))
        let msgData;
        try {
            msgData = JSON.parse(event.nativeEvent.data) || {}
        } catch (error) {
            console.error(error)
            return
        }
        this[msgData.targetFunc].apply(this, [msgData]);
    }

    /**
     * 영화목록을 받아와서 화면에 전달한다.
     */
    getMovieList = msgData => {
        const option = {
            method: 'GET',
            timeout: 60000
        }
        let url = domain + msgData.data.url

        fetch(url, option)
            .then(res => {
                return res.json()
            })
            .then(response => {
                console.log('<====== response', response)
                msgData.isSuccessfull = true
                msgData.data = response
                this.appWebview.postMessage(JSON.stringify(msgData), '*');
            })
            .catch(error => {
                console.log(error)
            })
    }

    render() {
        return (
            <View style={styles.container}>
                <WebView
                    style={styles.webview}
                    source={this.state.indexPage}
                    originWhitelist={['*']}
                    ref={webview => this.appWebview = webview}
                    onMessage={this.onWebViewMessage}
                    javaScriptEnabled={true}
                    useWebKit={true}
                />
            </View>
        );
    }
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        paddingTop: 30
    },
    webview: {
        flex: 1
    }
});

iphone에서 Command + D를 누르고 Remote JS debugging을 확성화시키면 크롬 개발자툴에서 디버깅을 할 수 있습니다.

통신하기 버튼을 누르면 크롬 콘솔에서 movie.json을 받아옵니다.

그런데 iOS에서는 react native에서 화면으로 post message를 던져도 받지를 못합니다. 

예전에 react native에 Webview가 포함되어 있을때는 문제 없이 받았는데

react-native-webview로 별도로 독립하고 나서는 post message를 못받습니다. -_-

이번에 react-native 0.6으로 버전업 해서 혹시나 iOS에서 해봤는데..마찬가지네요.

이걸 해결한 다른 webview가 있는데..그건 다음에 ㅋㅋ (급하신분은 댓글 남겨주시면 알려드릴께요.)

 

Android에서 테스트 계속 해보겠습니다.

$ react-native run-android

통신하기 버튼을 누르면 react native의 fetch 함수로 통신해서 결과값을 javascript 화면으로 전달해 주는 로직이었습니다.

 

package.json 최종입니다.

{
  "name": "secondProject",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest"
  },
  "dependencies": {
    "react": "16.8.3",
    "react-native": "0.59.8",
    "react-native-webview": "^5.12.1"
  },
  "devDependencies": {
    "@babel/core": "7.5.4",
    "@babel/runtime": "7.5.4",
    "babel-jest": "24.8.0",
    "jest": "24.8.0",
    "metro-react-native-babel-preset": "0.55.0",
    "react-test-renderer": "16.8.3"
  },
  "jest": {
    "preset": "react-native"
  }
}

Firebase 목록

반응형
Comments