-
음식점 추천 챗봇 만들기1 - Frontend 구조졸업작품 2021. 8. 23. 12:49
Notion 으로 이동했습니다
졸업작품 - 음식점추천 챗봇앱
2021.06.25 ~ Github : https://github.com/su1715/food-select-chatbot React Native, Dialogflow, Flask, AWS EC2
hill-bovid-56d.notion.site
첫 글을 작성한 후로 시간이 많이 흘렀고 코드도 꽤 많이 쌓였다.
이제 중요기능들은 어느정도 구현이 되어서 정리해보려고한다.
음식점 추천 챗봇은 결정이 어려운 사람들을 반영하여 "뭐먹을까? 아무거나!"를 이름으로 정했다
Frontend 는 React Native (EXPO)
Backend 는 Flask를 사용한다
이번 게시물에서는 Frontend 코드를 정리해보겠다. 구조는 다음과 같다
App.js
(로그인하지 않은 경우)
└AuthScreen
└SignUpScreen
└SignInScreen
(로그인한 경우)
└MainScreen
└ChatScreen
RN에서 화면간 이동을 하려면 React Navigation을 사용해야한다.
하지만 우리는 화면간 이동뿐만 아니라 로그인해야만 화면을 넘어갈수 있도록 구현해주어야한다.
매번 로그인하는 것은 번거로우므로 userToken이라는 것을 사용한다.
userToken은 서버에서 발행해주는 로그인인증이다. userToken 을 받아 로컬에 저장해놓으면 매번 로그인할 필요 없이 세션이 만료될때만 로그인해주면 된다. (이 프로젝트에서는 세션만료를 구현하지 않아서 로그인하면 쭉 사용할 수 있다.)
UserToken(로그인 정보)가 없으면 AuthScreen..을 보여주어 로그인하게 하고
UserToken이 있으면 MainScreen을 통해 ChatScreen 을 사용할 수 있게한다.
이를 위해서 React Navigation 공식 문서에서는 Auth-flow 에 대한 예제를 제공한다.
https://reactnavigation.org/docs/auth-flow/
https://reactnavigation.org/docs/auth-flow/
reactnavigation.org
코드설명
bootstrapAsync
React.useEffect(() => { const bootstrapAsync = async () => { let userToken; try { userToken = await SecureStore.getItemAsync("userToken"); } catch (e) { // Restoring token failed } dispatch({ type: "RESTORE_TOKEN", token: userToken }); }; bootstrapAsync(); }, []);
bootstrapAsync : 앱이 실행될때 SecureStore에 userToken이 있는지 확인하고 있으면 받아온다.
useReducer
const [state, dispatch] = React.useReducer( (prevState, action) => { switch (action.type) { case "RESTORE_TOKEN": return { ...prevState, userToken: action.token, isLoading: false, }; case "SIGN_IN": return { ...prevState, isSignout: false, userToken: action.token, }; case "SIGN_OUT": return { ...prevState, isSignout: true, userToken: null, }; } }, { //default isLoading: true, isSignout: false, userToken: null, } );
useReducer를 사용해 action에 따른 state 변화를 구현한다.
예를 들어 bootstrapAsync 에서 dispatch({ type: "RESTORE_TOKEN", token: userToken })를 했으므로
state는
{...prevState,
userToken: action.token,
isLoading: false,
} 로 변경된다.authContext
const authContext = React.useMemo( () => ({ signIn: async (data) => { const { userId, password } = data; const signin_info = { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json", }, }; if (userId && password) { fetch(url + "/signin", signin_info) .then((response) => response.json()) .then((response) => { if (response.result === "success") { SecureStore.setItemAsync("userToken", response.token); dispatch({ type: "SIGN_IN", token: response.token }); } else alert(response.error); }); } else { alert("입력 양식을 확인해주세요"); } }, signOut: async () => { await SecureStore.deleteItemAsync("userToken"); dispatch({ type: "SIGN_OUT" }); }, signUp: async (data) => { const { userId, username, password, repassword } = data; const signup_info = { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json", }, }; let result; if ( userId && username && password && repassword && password === repassword ) { fetch(url + "/signup", signup_info) .then((response) => response.json()) .then((response) => { if (response.result === "success") { SecureStore.setItemAsync("userToken", response.token); dispatch({ type: "SIGN_IN", token: response.token }); } else alert(response.error); }); } else { alert("입력 양식을 확인해주세요"); } }, }), [] );
AuthContext : signIn, signOut, signUp 했을 때 실행할 함수들을 담고 있다. 이는 AuthContext.Provider를 통해 아래 컴포넌트들에게 전해진다. 해당 컴포넌트들에서 각 함수를 사용하게 되면 아래 내용이 실행된다.
signIn : 서버에 보낼 정보를 signin_info 에 담는다. userId와 password가 모두 채워진 경우에만 fetch를 통해 서버에 정보를 전달한다. 서버에서 user정보를 확인하고 userToken을 보내주면 Secure Store(로컬저장소)에 저장하고 SIGN_IN action을 dispatch하여 state를 바꿔준다.
signOut : Secure Store에서 userToken 을 삭제하고 SIGN_OUT action을 dispatch한다.
signUp : 서버에 보낼 정보를 signup_info에 담는다. userId, password, username, repassword가 모두 채워졌고 password와 repassword가 같은 경우 signup_info를 서버에 보내고 userToken을 받는다. 회원가입을 하면 새로 로그인할필요 없이 자동 로그인이 되도록 하기 위해 SIGN_IN action을 dispatch 한다.
렌더링
const Stack = createStackNavigator(); export const AuthContext = React.createContext(); export default function App({ navigation }) { return ( <NavigationContainer> <AuthContext.Provider value={authContext}> <Stack.Navigator> {state.userToken == null ? ( <> <Stack.Screen name="Auth" component={AuthScreen} options={{ headerShown: false, }} /> <Stack.Screen name="SignIn" component={SignInScreen} options={{ headerShown: false, }} /> <Stack.Screen name="SignUp" component={SignUpScreen} options={{ headerShown: false, }} /> </> ) : ( <> <Stack.Screen name="Main" component={MainScreen} options={{ headerShown: false, }} initialParams={{ token: state.userToken }} /> <Stack.Screen name="Chat" component={ChatScreen} options={{ headerShown: false, }} initialParams={{ token: state.userToken }} /> </> )} </Stack.Navigator> </AuthContext.Provider> </NavigationContainer> ); }
state.userToken == null ? ():()
: 삼항연산자를 사용하여 userToken 이 없는경우 AuthScreen, SignInScreen, SignUpScreen 에 접근할 수 있고
있는 경우 MainScreen, ChatScreen에 접근할 수 있다.
AuthContext.Provider : value 값을 아래 컴포넌트들에게 전달한다. 아래 컴포넌트에서 signUp, signOut, signIn함수를 사용하여 state를 바 수 있게 된다.
initialParams : Navigation을 사용할때는 파라미터를 마음대로 전달할수 없고 initialParams을 통해 보내야한다.
전체코드
import * as React from "react"; import { NavigationContainer } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; import * as SecureStore from "expo-secure-store"; import MainScreen from "./src/MainScreen"; import ChatScreen from "./src/ChatScreen"; import AuthScreen from "./src/AuthScreen"; import SignInScreen from "./src/SignInScreen"; import SignUpScreen from "./src/SignUpScreen"; import { url } from "./env"; //서버 주소 const Stack = createStackNavigator(); export const AuthContext = React.createContext(); export default function App({ navigation }) { const [state, dispatch] = React.useReducer( (prevState, action) => { switch (action.type) { case "RESTORE_TOKEN": return { ...prevState, userToken: action.token, isLoading: false, }; case "SIGN_IN": return { ...prevState, isSignout: false, userToken: action.token, }; case "SIGN_OUT": return { ...prevState, isSignout: true, userToken: null, }; } }, { isLoading: true, isSignout: false, userToken: null, } ); React.useEffect(() => { const bootstrapAsync = async () => { let userToken; try { userToken = await SecureStore.getItemAsync("userToken"); } catch (e) { // Restoring token failed } dispatch({ type: "RESTORE_TOKEN", token: userToken }); }; bootstrapAsync(); }, []); const authContext = React.useMemo( () => ({ signIn: async (data) => { const { userId, password } = data; const signin_info = { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json", }, }; if (userId && password) { fetch(url + "/signin", signin_info) .then((response) => response.json()) .then((response) => { if (response.result === "success") { SecureStore.setItemAsync("userToken", response.token); dispatch({ type: "SIGN_IN", token: response.token }); } else alert(response.error); }); } else { alert("입력 양식을 확인해주세요"); } }, signOut: async () => { await SecureStore.deleteItemAsync("userToken"); dispatch({ type: "SIGN_OUT" }); }, signUp: async (data) => { const { userId, username, password, repassword } = data; const signup_info = { method: "POST", body: JSON.stringify(data), headers: { "Content-Type": "application/json", }, }; let result; if ( userId && username && password && repassword && password === repassword ) { fetch(url + "/signup", signup_info) .then((response) => response.json()) .then((response) => { if (response.result === "success") { SecureStore.setItemAsync("userToken", response.token); dispatch({ type: "SIGN_IN", token: response.token }); } else alert(response.error); }); } else { alert("입력 양식을 확인해주세요"); } }, }), [] ); return ( <NavigationContainer> <AuthContext.Provider value={authContext}> <Stack.Navigator> {state.userToken == null ? ( <> <Stack.Screen name="Auth" component={AuthScreen} options={{ headerShown: false, }} /> <Stack.Screen name="SignIn" component={SignInScreen} options={{ headerShown: false, }} /> <Stack.Screen name="SignUp" component={SignUpScreen} options={{ headerShown: false, }} /> </> ) : ( <> <Stack.Screen name="Main" component={MainScreen} options={{ headerShown: false, }} initialParams={{ token: state.userToken }} /> <Stack.Screen name="Chat" component={ChatScreen} options={{ headerShown: false, }} initialParams={{ token: state.userToken }} /> </> )} </Stack.Navigator> </AuthContext.Provider> </NavigationContainer> ); }
'졸업작품' 카테고리의 다른 글
음식점 추천 챗봇 만들기4 - frontend + backend 회원가입 (0) 2021.08.25 음식점 추천 챗봇 만들기3 - frontend + Backend 로그인 (Flask) (0) 2021.08.24 음식점 추천 챗봇 만들기2 - Frontend 회원가입, 로그인 (0) 2021.08.24 음식점 추천 챗봇만들기0 (0) 2021.06.25