최현일
194
2021-07-19 13:19:12 작성 2021-07-19 14:56:29 수정됨
0
364

앱만들기. 공부해보세요~. 리액트네이티브(웹뷰) + 리액트네비게이션 스크린(스마트기기 기능활용)


안녕하세요.

앱만들기 관련, 강좌를 적어보네요.  강좌? 나만 알고 있기도 그렇고... 뭔가, 도움이 되고 싶어서 적어봄니다. 저도 개발하면서, 댓글 달지 못하고 전에 개발자분들 노트보면서, 많이 잘 활용하고 있죠~.


 앱만들기 초심자 분들. 관심있으신 분들이시라면, 가벼운 마음으로 그냥 한번 훑어보는 식으로 봐 주세요. 한번에 다 알 수는 없겠죠. 도움이 되었으면 좋겠네요.



기본이 되는, 리액트 네이티브. 사이트입니다. 이 사이트를 보면서, 공부하면 잘 될거라고 생각합니다~.

https://reactnative.dev/


리액트 네이티브, 개발 환경은 이 사이트를 참조해보세요~.

https://dev-yakuza.posstree.com/ko/react-native/install-on-windows/




웹뷰. 사이트를 만든 다음에, 해당 주소를 적기만해도, 앱이 됨니다~. 대신에, 반응형 웹으로 만들어야하겠죠.


부트스트랩을 활용해서 만들 수 있습니다. 공부해보세요.

https://getbootstrap.com/


 사이트를 만들 때에, github의 기능을 활용하는 것도 좋습니다. visual code를 사용한다면, git을 프로젝트에 연결을 하면, private로든, 일반으로든 해서, git이 생성이 되어서, 커밋과 푸시를 할 수 있는데요.


https://github.com/infott2t/smartFactory-ex


부트스트랩으로 만들었습니다. 그리고, 깃헙에서, 이런 주소를 사용하면, 사이트로 바꿀 수 있습니다. index.html파일이 위치하게 만들어 주세요.

https://infott2t.github.io/smartFactory-ex/

인터넷 검색하시면, 해당 내용찾으실 수 있을 검니다.


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

class App extends Component {
  render() {
    return (
      <WebView
        source={{uri: 'http://jcoop.xyz'}}
       
      />
    );
  }
}

export default App;


그래서, 그 주소로 이렇게 적어주면, 웹뷰가 됨니다.


웹뷰는, 


https://reactnative.dev/docs/0.63/webview


위의 링크에 설명이 잘 되어있습니다. 거기에 github링크를 들어가면, 해당 자세한 이용방법이 적혀있어요.


여기서, 웹뷰와 리액트네이티브 사이의 통신은 어떻게 할 수 있나... 또, 웹뷰에서 빠져나와서, 카메라던지, QR코드라던지... 그런 화면은 어떻게 바꿀까. 이런 부분이 있죠.


웹뷰에서 회원의 아이디라던지, 그런 부분을 가져올 수 있어요. String으로 넘겨받을 수 있는데요. String은... 바로 JSON을 말하는 것이죠. PostMessage로 넘길 수 있습니다.


 String을 JSON으로 바꾸기 위해서는, 이 부분을 신경써주면 됨니다.


<script>
$(document).ready(function(){
var email = $("#email").text()

   setTimeout(function () {
            window.ReactNativeWebView.postMessage('{"email" : "'+email+'"}')
          }, 2000)


});

</script>


index, 어느 뷰 페이지에서 #email이라는 아이디를 가진 태그를 display:none으로 숨겨서 넘긴 것인데요.

 $(document).ready()로 실행되어, 페이지가 실행될 때에 항상 실행이 되게 됨니다.

 window.ReactNativeWebView.postMessage(스트링) 이런 식으로 넘길 수 있습니다. 단, 

{ "key" : "value" } 이와 같은 형태로 넘겨야 합니다. key와 value값을 "". 쌍따옴표로 감싸줘야하죠.


실제로 받을 때에는...


<WebView
        ref={(ref) => (this.webview = ref)}   //자기 자신. ref.
        source={{ uri: 'http://호스팅주소:8080' }}       //url 주소. 
        onNavigationStateChange={this.handleWebViewNavigationStateChange}     //url주소가 바뀌면...
        onMessage={(event) => {
          
          var data = event.nativeEvent.data;
          
          this.state.data = JSON.parse(data);
          //alert(this.state.data.email + ' well~.');
          AsyncStorage.setItem('data', JSON.stringify(this.state.data));
          //console.log(this.state.data.email);
             
        }}
      />


위에 적은 것은 WebView 태그 입니다. 이것이 작동을 하게 하죠. 실제로 받을 때에... 

var data = event.nativeEvent.data

이런 식으로 받게 됨니다. 웹뷰에는 url이 적히구요. 그래서, 특정한 어느 페이지에 들어가서, script안에, window.ReactNativeWebView.postMessage(스트링)  이 코드가 나오면, 값을 그때마다 가져옴니다. var data = event.nativeEvent.data 이것을 통해서요.

 그래서, 스트링을 JSON으로 파싱해줌니다. JSON.parse(data). 이렇게 하면, data.email 이런 식으로 접근이 가능하게 되는 것이구요. 또, 리액트 네이티브에서, 스크린, 클래스가 바뀌어도 해당 값을 가져올 수 있게, AsyncStorage로 저장하는 모습입니다. AsyncStrorage로 저장할 때에도 String으로 저장하는데요.

 var data = event.nativeEvent.data;  // 웹호스트에 데이터를 가져온다.
          
this.state.data = JSON.parse(data);  //스트링인 data를 parse해서, state에 저장하였다.
          //alert(this.state.data.email + ' well~.')
AsyncStorage.setItem('data', JSON.stringify(this.state.data)); //data가 JSON이라서, 다시 string으로 변환(JSON.stringify())해서 저장해야한다. AsyncStorage에 저장할때.



실제로 값을 가져올 때에는,


AsyncStorage.getItem('data').then((value)=>{
        var value2 = JSON.parse(value);
        this.setState({data:value2}); 
      });
//alert('email, ' + this.state.data.email)


위와 같이, then을 통해서 값을 가져옴니다. 값을 가져올 때에도, 스트링 값으로 저장이 되어있어서, JSON.parse()가 필요합니다. 


그럼, 어떻게 웹뷰에서 여러 스크린으로 넘어갈 수 있을까요?


그방법은... 

https://github.com/react-native-webview/react-native-webview/blob/master/docs/Guide.md


웹뷰, 설명에 잘 나와 있습니다. 그리고, react navigation이 필요합니다. 이것도 패키지인데요.

https://reactnavigation.org/docs/4.x/getting-started

v4.0버젼을 올려봄니다. 5.0도 나왔는데요. 4.0의 자료가 많고, 또 배울때에, 4.0으로 배워서, 4.0으로 올리게 되는군요. 여기에 getting-started를 해보신다면, 익숙해지실 수 있을거예요. 

https://reactnavigation.org/docs/4.x/hello-react-navigation

그래서, 미니 프로젝트로 위의 hello-react-navigation이 되었다면... 어느정도 감을 잡으실 수 있을 거예요.


그리고, 리액트 네이티브에서 WebView태그를 살펴봅시다.


import React, { Component } from 'react';
import { WebView } from 'react-native-webview';
import AsyncStorage from '@react-native-async-storage/async-storage';
import HandScreen  from './screen/HandScreen';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';



class WebScreen extends Component {
  state = {data : ''};

  constructor(props) {  //생성자 정의,
    super(props);  //상위 생성자를 가져온다. 아마도, react navigation의 속성들이 더해져서 들어오겠죠.
     
  }

  webview = null;

  render() {
    return (
      <WebView
        ref={(ref) => (this.webview = ref)}   //자기 자신. ref.
        source={{ uri: 'http://웹호스팅:8080/user/index' }}       //url 주소. 
        onNavigationStateChange={this.handleWebViewNavigationStateChange}     //url주소가 바뀌면...
        onMessage={(event) => {
          
          var data = event.nativeEvent.data;
          
          this.state.data = JSON.parse(data);
          //alert(this.state.data.email + ' well~.');
          AsyncStorage.setItem('data', JSON.stringify(this.state.data));
          //console.log(this.state.data.email);
             
           
          
          
        }}
      />
    );
  }

 

 

  handleWebViewNavigationStateChange = (newNavState, props) => {   //주소가 바뀌면 항상 여기로 와서 데이터가 저장되요.
    // newNavState looks something like this:
    // {
    //   url?: string;
    //   title?: string;
    //   loading?: boolean;
    //   canGoBack?: boolean;
    //   canGoForward?: boolean;
    // }
    const { url } = newNavState;
    if (!url) return;                       //url이 다르면, 돌아가기.

    // handle certain doctypes                                
    if (url.includes('qr')) {                  
      this.webview.stopLoading();   
      
      AsyncStorage.getItem('data').then((value)=>{
        var value2 = JSON.parse(value);
        this.setState({data:value2}); 
      });
      //alert('email, ' + this.state.data.email)

      const newURL = url.replace("/qr","");                      
      const redirectTo = 'window.location = "' + newURL + '"';         
      this.webview.injectJavaScript(redirectTo);
     
    }
    if (url.includes('hand')) {                  
      this.webview.stopLoading();   
      this.props.navigation.navigate('HandSc');
      AsyncStorage.getItem('data').then((value)=>{
        var value2 = JSON.parse(value);
        this.setState({data:value2}); 
      });
      //alert('email, ' + this.state.data.email)

      const newURL = url.replace("/hand","");                      
      const redirectTo = 'window.location = "' + newURL + '"';         
      this.webview.injectJavaScript(redirectTo);
     
    }
    if (url.includes('move')) {                   
      this.webview.stopLoading();   
      AsyncStorage.getItem('data').then((value)=>{
        var value2 = JSON.parse(value);
        this.setState({data:value2}); 
      });
      alert('email, ' + this.state.data.email)

      const newURL = url.replace("/move","");                      
      const redirectTo = 'window.location = "' + newURL + '"';         
      this.webview.injectJavaScript(redirectTo);
     
    }

    
  };
}


const AppNavigator = createStackNavigator({
  WebSc: {
    screen: WebScreen,
    navigationOptions: {headerShown: false}
  },

  HandSc : {
    screen: HandScreen,
    navigationOptions: {title: "손 사진찍기",
                        }
  }
});

const AppContainer = createAppContainer(AppNavigator);

class App extends Component {
  render() {
    return <AppContainer/>;
  }
}

export default App;



실제로, 제가 사용하고 있는 코드입니다.  원래 webView. github에 사용법을 보면, 

handleWebViewNavigationStateChange = (newNavState, props) => {

(newNavState) 이 값만 넘기는데, props값을 넘겨서, 리액트네비게이션도 되게 만든 것이죠~.




if (url.includes('hand')) {                  
      this.webview.stopLoading();   
      this.props.navigation.navigate('HandSc');
      AsyncStorage.getItem('data').then((value)=>{
        var value2 = JSON.parse(value);
        this.setState({data:value2}); 
      });
      //alert('email, ' + this.state.data.email)

      const newURL = url.replace("/hand","");                      
      const redirectTo = 'window.location = "' + newURL + '"';         
      this.webview.injectJavaScript(redirectTo);
     
    }


일부를 빼어내었는데요.. url 내에, hand라는 글자가 들어있게 되면, 위의 if문 안으로 들어가게 됨니다. 그 다음, this.webview.stopLoading()으로, 웹뷰를 멈춰주고, this.props.navigation.navigate('HandSc'); 이 문장을 통해서, 스크린으로 이동시켜줘요. 그리고, 아래에 보면, const newURL ... 해서, redirectTo. this.webview.injectJavaScript로, 또 다른 url로 이동을 시켰죠. 그런데, /hand라는 부분을 replace를 통해 빼내었습니다. 이것은 무슨말일까요? 바로, 전의 url. /hand url이 들어오기전의 url. back버튼을 한번 누른 것같이, 뒤로 돌아간 상태로 되어있게 만든 것 입니다.  버튼이 QR코드 입력인 경우, /qr을 추가했고, 손 사진찍기라는 버튼에는 /hand. 추가하구요. 이런 식의 url이였습니다. 그래서, 마지막 url인 /qr을 빼면, QR버튼 누르기전 화면. 이렇게 되는 것이였죠.

 이렇게 하면, 스크린으로 넘긴 상태에서, 뒤로. 스크린 헤드부분에, 화살표 있잖아요. 또는 Back버튼. 그것을 누르게 되면, 자연스럽게, 다시 전 화면인, webView화면이 되어있게 되죠. 설명은 힘든데, 실제로 만들어본다면, 잘 알 수 있을거라고 생각해보네요.

 스프링부트의 컨트롤러에, /hand를 넣든, 없든지, 리다이렉트를 해서 같은 view화면으로 넘기는 방식을 사용도 되구요.

 

 위의 방식을 사용해서, 웹뷰에서, 스크린으로 갔다가, 스크린에서 웹뷰로 가기. 이것이 가능하게 됨니다. 스크린으로 간다는 것은, 스크린 안에 카메라기능을 넣었다면, 사진을 찍을 수 있게 되구요. 여러 스마트기기의 기능을 활용할 수 있게 되는것이죠~. 실제 구현한 모습. 올려보네요.

[앱프로그래밍] 웹뷰화면에서 리액트 네비게이션 화면 이동 테스트 (https://youtu.be/J56TVKDiLjE)

그때 사용했던 코드입니다. 위에 설명했던 코드와는 조금 다름니다.

Screen을 한 곳에 구현했군요. 또, qr.html이런 형식 처럼, 빈 html페이지를 만들고, 실제 리액트네이티브 웹뷰 url change감지를 했습니다. 스프링부트가 아니라, node의 http-server로 구동한 웹페이지였는데요. 스프링부트라면, 전의 코드에서 설명했던, url에 /qr, /hand, /move가 있어도, 같은 뷰로 해서 컨트롤러에 추가해주는 형식이 맞는 것 같더라구요. 실제 만들어보시면, 알수 있을 거라고 생각해봄니다.


import React, { Component } from 'react';
import { WebView} from 'react-native-webview';
import { Button, View, Text } from 'react-native';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';

const domain = 'http://웹호스팅:8083';
class WebScreen extends Component {
  
  constructor(props){
    super(props);
    
  }
 
   
  render(){
   // const {navigation} = this.props;
    //const urlValue = navigation.getParam('uriSetting','웹호스팅:8083/index.html');
    
    
    //this.setState({uri: urlValue});
    return(
      <View style={{flex:1}}>
      <WebView ref={(ref)=> {this.webview =ref;}}
      source={{uri:'http://웹호스팅:8083'} }
      style={{ marginTop: 0 }}
      onNavigationStateChange={this.handleWebViewNavigationStateChange}
      onMessage={(event) => {alert(event.nativeEvent.data)
      
      }}
      />
    </View>
    );
  }

  handleWebViewNavigationStateChange = (newNavState, props) => {
    const {url} = newNavState;
    
    if(url.includes('qr.html')){
      alert(url)
      this.webview.stopLoading();
     
      this.props.navigation.navigate('QRSc');
      setTimeout(() => {
        const newURL = 'http://웹호스팅:8083/simulation.html';
      const redirectTo = 'window.location = "' + newURL + '"';
      this.webview.injectJavaScript(redirectTo);
      }, 1000); 
    }
    if(url.includes('hand.html')){
      this.webview.stopLoading();
      this.props.navigation.navigate('HandSc');
      setTimeout(() => {
        const newURL = 'http://웹호스팅:8083/simulation.html';
      const redirectTo = 'window.location = "' + newURL + '"';
      this.webview.injectJavaScript(redirectTo);
      }, 1000);
      
       
    }
    if(url.includes('move.html')){
      this.webview.stopLoading();
      this.props.navigation.navigate('MoveSc');
      setTimeout(() => {
        const newURL = 'http://웹호스팅:8083/simulation.html';
      const redirectTo = 'window.location = "' + newURL + '"';
      this.webview.injectJavaScript(redirectTo);
      }, 1000); 
    }
  }
}
 
class MoveScreen extends Component{
  constructor(props){
    super(props)
  }
  

  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Move Screen</Text>
        <Button
          title="Go back"
          onPress={()=>this.props.navigation.navigate('WebSc')}
        />
      </View>
    );
  }
}
 
class HandScreen extends Component{
  constructor(props){
    super(props)
  }
  

  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Hand Screen</Text>
        <Button
          title="Go back"
          onPress={()=>this.props.navigation.navigate('WebSc')}
        />
      </View>
    );
  }
}
class QRScreen extends Component {

  constructor(props){
    super(props)
  }
   

  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>QR Screen</Text>
        <Button
          title="Go back"
          onPress={()=>this.props.navigation.navigate('WebSc')}
        />
      </View>
    );
  }
}


const AppNavigator = createStackNavigator({
  
  WebSc: {
    screen: WebScreen,
    navigationOptions: {headerShown: false}
  },
  QRSc: {
    screen: QRScreen,
    navigationOptions: {title: "QR코드 입력", headerLeft: ()=>null}
  },
  HandSc: {
    screen: HandScreen,
    navigationOptions: {title: "손 사진찍기", headerLeft: ()=> null}
  },
  MoveSc: {
    screen: MoveScreen,
    navigationOptions: {title: "이동하기",
                        headerLeft: ()=> null}
  }

});

const AppContainer = createAppContainer(AppNavigator);

class App extends Component {
  render(){
    return <AppContainer/>;
  }
}

export default App;



 웹뷰. OAuth를 구현해 놓은 웹프로그램을 웹뷰에 올리고... 이렇게 하게 되면, 카메라등의 스마트기기 활용까지 가능하게 되서, 앱이라고 부를 수 있게되죠~. OAuth의 구현은, 스프링 부트와 AWS로 혼자 구현하는 웹 서비스라는 책을 추천해 봄니다. 거의 다 나와 있다고 생각해요~. 책 홍보가 되었는데... 우연히 산 책인데, 좋은 책 같습니다.


 설명을 더 잘 적고 싶은데... 잘 못적은 것도 같네요~. 또, 위에 참조한 사이트.. 스마트팩토리에 관한 내용이 있는데요. 실제, 일자리가 좋아지겠죠~. 한번 개발해보세요~. 스마트팩토리.


앱 개발하는데에, 도움이 되었으면 좋겠네요. 공부해보세요~. 


저의 글, 봐 주셔서 감사합니다.





1
  • 댓글 0

  • 로그인을 하시면 댓글을 등록할 수 있습니다.