react-native(expo) Webview 안드로이드 뒤로가기 두번 클릭시 종료

이지원·2022년 5월 26일
2
post-thumbnail

이번 프로젝트에서 내가 맡은 부분은
react-native의 webview를 활용한 어플리케이션 내에서
안드로이드 뒤로가기버튼을 클릭하였을때 뒤로가기 기능을 적용시켜 보고자 한다.
위 사진 네이버 웹툰 안드로이드앱 처럼 똑같은 기능을 만들어보고자 하였다

구현해야할 기능

1.페이지 단위로 뒤로가기 (ex: /board url -> /home url 이동)
2.페이지에 최상위에 도달하였을때 뒤로가기 버튼을 누르면 뒤로가기를 한번더 누르면 종료하시겠습니까? 맨트 보여주기
3.2번의 케이스일때 맨트가 노출될때 누르면 종료 그게아니면 다시 맨트노출

뒤로가기

뒤로가기 기능은 BackHandler라는 라이브러리를 이용할 것이다.
expo를 이용하고 있기 때문에 자세한 설명은 아래 공식문서를 확인해 보자.
링크

function App() {
const [mainUrl, setMainUrl] = useState(''); //url을 담을 변수
const webview = useRef(null); //우리 웹사이트에 접근하여 웹뷰 함수를 호출하게끔 도와준다.
  const onUrlChange = (url) => { //url을 변경해주는 함수
    setMainUrl(url); 
  }
useEffect(() => {
    BackHandler.addEventListener('hardwareBackPress', onAndroidBackPress); //뒤로가기 버튼을 클릭하였을때,함수를 호출하는 이벤트 등록
    return () => {
      BackHandler.removeEventListener('hardwareBackPress', onAndroidBackPress); //뒤로가기 함수를 해제하는 이벤트 등록
    };
    
  }, [mainUrl]);
 	
 }

메인 로직은 이렇게 구성된다.

우리의 웹사이트에서 data를 webview에 보내주기

expo webview에서는
postMessage라는 기능이 있다.
우리의 웹사이트에서 우리의 웹뷰앱으로 통신을 하는 방법이라고 보면된다.
우리는 뒤로가기 버튼을 클릭하면 이전 url로 돌아가고 더이상 이전 url이 없는 최상위 경로 인경우 뒤로가기 클릭 시 맨트 노출 후 종료 해주기 때문에 우리의 웹사이트에서 페이지가 이동할때 마다 우리의 웹뷰앱에 현재 page를 보내주게된다.
우리의 webview는 Angular라는 프레임워크를 이용하였는데
Angular에서 url이 변경될 때마다 우리의 webview에 data를 보내주는 코드이다
아래의 ReactNativeWebview.postMessage() 인자값에 데이터를 보내줄 수 있다.

Angular, app.component.ts 내부..

export class AppComponent {
  constructor(public _router: Router) {
    this._router.events.subscribe((event) => {
      if (event instanceof NavigationStart) {
        try {
          if (((<any>window).ReactNativeWebView)) {
            let sendData = 'url' + "#" + event.url; //url#우리페이지
            (<any>window).ReactNativeWebView.postMessage(sendData); 
          }
        } catch(e) {
          console.log(e);
        }
      }
    });
  }

webview 에서 웹사이트에서 보낸 data 받아오기

웹사이트에서 postMessage 함수로 data를 보냈으니 이
webview앱에서 data를 받아올 때는 onMessage라는 기능으로 data를 받아 올 수 있다.

<WebView
          source={{ uri: '우리웹사이트 주소'}}
          ref={webview}
          onMessage={(event) => {
            let data = event.nativeEvent.data;
            let final = data.split('#');
            switch (final[0]) {
              case 'shareUrl' :
                onShare(final[1]);
                break;
              
              case 'sharePhoto' :
                sharePhoto(final[1]);
                break;
              
              case 'downloadPhoto' :
                downloadFile(final[1]);
                break;

              case 'uploadPhoto' :
                break;
              case 'url':
                onUrlChange(final[1]);
                console.log('보내주는url:', final[1]);
                break;
            }
          }}          
        />

웹뷰 통신 설명

let data = event.nativeEvent.data;
let final = data.split('#');
해당 부분의 의미는 현재 data 라는 변수에는 'url#우리주소' 문자열이 담겨져 있다.
또한 split 함수를 사용하여 final은 ['url','우리주소'] 이런 배열로 담겨져 있기 때문에
각각의 case마다 다른 함수를 호출 시키게 해놓았다.

뒤로가기 두번

우선 안드로이드 앱에서 자주 보이는 Toast popup 기능을 이용할 것이다.
expo를 이용하고 있기 때문에 자세한 설명은 아래 공식문서를 확인해 보자.
링크

import { SafeAreaView, StatusBar, Share,BackHandler,ToastAndroid } from 'react-native';


function App() { 
	const toastWithDurationHandler = () => {
    ToastAndroid.show("'뒤로' 버튼을  한번 더 누르시면 종료됩니다.", ToastAndroid.SHORT);
  };
  
  let time = 0; // 맨트 노출 시간
  const onAndroidBackPress = () => {
    if (!webview.current) {
      return;
    }
    if (mainUrl === '/main' || mainUrl === '/') { //최상위 url 일때 뒤로가기 두번 클릭시 나가기
      time += 1;
        toastWithDurationHandler(); // 뒤로가기 토스트 바 
      if (time === 1) {
        setTimeout(() => time = 0, 2000);
      }
      else if (time === 2) {
        BackHandler.exitApp(); // 어플 종료
        return false;
      }
    } else {
      webview.current.goBack(); // 최상이 url이 아닌경우 웹페이지 뒤로가기
      return true;
    }
    return true;
  };
  useEffect(() => {
    BackHandler.addEventListener('hardwareBackPress', onAndroidBackPress);
    return () => {
      BackHandler.removeEventListener('hardwareBackPress', onAndroidBackPress);
    };
  }, [mainUrl]);
}

웹뷰 전체코드

import  React, {useRef,useEffect,useState} from 'react';
import { WebView } from 'react-native-webview';
import { SafeAreaView, StatusBar,BackHandler,ToastAndroid } from 'react-native';

const toastWithDurationHandler = () => {
    ToastAndroid.show("'뒤로' 버튼을  한번 더 누르시면 종료됩니다.", ToastAndroid.SHORT);
  };

export default function App() {
  const INJECTED_JAVASCRIPT = `(function() {
  const meta = document.createElement('meta'); meta.setAttribute('content', 'initial-scale=1, maximum-scale=1, user-scalable=no'); meta.setAttribute('name', 'viewport'); document.getElementsByTagName('head')[0].appendChild(meta);
})();`;
  
  const [mainUrl, setMainUrl] = useState(''); 

  const onUrlChange = (ref) => {
    setMainUrl(ref);
  }
  
  //뒤로가기 처리
  const webview = useRef(null);
  let time = 0;
  const onAndroidBackPress = () => {
    if (!webview.current) {
      return;
    }
    if (mainUrl === '/main' || mainUrl === '/') {
      console.log('나갈 수 있는 상태');
      time += 1;
        toastWithDurationHandler(); // 뒤로가기 토스트 바 
      if (time === 1) {
        setTimeout(() => time = 0, 2000);
      }
      else if (time === 2) {
        console.log('어플종료');
        BackHandler.exitApp();
        return false;
      }
    } else {
      console.log('뒤로가기')
      webview.current.goBack();
      return true;
    }
    return true;
  };
  //뒤로가기 처리
  useEffect(() => {
    BackHandler.addEventListener('hardwareBackPress', onAndroidBackPress);
    return () => {
      BackHandler.removeEventListener('hardwareBackPress', onAndroidBackPress);
    };
    
  }, [mainUrl]);
  return (
    <>
        <StatusBar barStyle="black-content" hidden />
        <SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
        <WebView
          source={{ uri: 'https://www.mycozydogs.com'}}
          injectedJavaScript={INJECTED_JAVASCRIPT}
          ref={webview}
          allowsbackforwardnavigationgestures={true}
          scalesPageToFit={true}
          onMessage={(event) => {
            let data = event.nativeEvent.data;
            let final = data.split('#');
            switch (final[0]) {
              case 'url':
                onUrlChange(final[1]);
                break;
            }
          }}          
        />
        </SafeAreaView>
    </>
  );
}

완성된 사진

메인 페이지를 제외한 다른곳에서 뒤로가기 버튼 클릭시 뒤로이동

profile
안녕하세요 피드백은 언제나환영입니다.

0개의 댓글