반응형

React Native와 FireBase연동하기 

https://rnfirebase.io/

1. 파이어베이스에 새 프로젝트 만들기

 

2. 파일 설치

npm install --save @react-native-firebase/app

React Native와 FireBase연동하기 (Android SetUp)

3. 파이어베이스 프로젝트에서 플렛폼을 추가함.

  3-1. 플렛폼을 추가할때 Android package name을 추가해야하는데 앱안에 있는 것과 같은 이름을 사용해야함 ./android/app/build.gradle에서 다음 부분을 찾아 ID 이름을 입력

defaultConfing{
  applicationId "id 이름"

 

  3-2. android nickname은 알아서 지어주고 Debug signing certificate SHA-1가 필요한데 이는 구글 인증을 하거나 핸드폰번호나 Dynamiv Link로 인증하고 싶다면 필요하다.

이를 하기 위해서 VS CODE로 돌아와 터미널에서 다음을 입력하면 터미널에서 Text가 나온다.

cd android && ./gradlew signingReport

그 후 Task :app:signingReport라는 걸 찾아주고 SHA1의 부분을 찾아 복사하고 파이어베이스에 붙어주면 파일을 다운받을 수 있는데 이 파일을 앱안에 넣어줘야한다.

 

4. 다음 버튼을 누르고 ./android/build.gradle에 가서 google()과 classpath() 부분을 확인해 주고 없는건 추가해준다.

  ./android/app/build.gradle에가서도 화면에 나와있는 부분을 추가해주어야한다.

 

5. npm run android를 해주어 잘 설치가 되었는지 확인한다.

 

React Native와 FireBase연동하기 (IOS SetUp)

3. 파이어베이스 프로젝트에 ios추가

  3-1. ios bundle ID를 입력해야하는 데 이는 XCode안에서 찾아야한다. 프로젝트 내의 ios폴더를 finder로 열어 .xcworkspace나 .xcodeproj 파일을 눌러 실행시킨다. Xcode에서 Pods 말고 프로젝트 이름의 폴더를 클릭하면 Identity에 Bundle Identifier가 있다. (xcode창은 끄지 말것).

app nickname은 원하는 걸 작성하면되고 app store ID는 앱스토어  이미 출시가됬다면 그 번호를 작성해주면 된다.

 

4. 다음 버튼을 누르고 기다리면 다운로드 해야할 파일이 나올것이다. 그 파일을 다운로드 하고 이 파일을 프로젝트에다 추가해야하는데 xcode에서  프로젝트로 가서 오른쪽클릭해서 add files to '프로젝트 명'을 누르고 다운받은 파일을 누르고 Copy items if needed , create groups 항목에 체크하고 add를 누른다.

 

5. ./ios/{projectName}/AppDelegate.mm file (or AppDelegate.m)을 열어주고 다음을 추가

#import <Firebase.h>

didFinishLaunchingWithOptions 라는 function을 찾아주고 

[FIRApp configure];

를 넣어준다. 넣을 수있는공간은 comment으로 넣으라고 써져있다.

쓰고 나서 다음을 터미널에 작성하여 실행

npx pod-install

그리고 Xcode 를 닫아주고 Use version on Disk 클릭해주고 프로젝트를 실행시켜 문제가 없는지 확인한다.

npm run ios

 

firebase의 기능을 사용하기 위해서는 각 기능들을 설치를 해줘야한다. 

728x90
반응형

interpolate - extrapolate

interpolate으로 동작의 변화를 주는데 내가 정한 수치보다 더 나아가서 계속 변화가 일어날때 그것을 방지하고자 사용한다. 수치보다 더 나아가고 싶다면 사용을 안해도 무방하다

extrapolate에는 3가지가 있는데

첫째로 "extend"는 끝이 한계치를 넘어서 계속 진행하는것이다.

둘째로 "identity"는

셋째로 "clamp"는 시작과 끝 점이 하나씩 있어서 다다르면 더이상 진행되지 않는다.

 

restSpeedThreshold 속도 임계점. 애니메이션을 끝난걸로 간주하는 속도 

restDisplacemetnThreshold 거리 임계값 애니메이션이 끝난걸로 간주하는 거리

 

Realm SDK

백엔드가 없이 데이터베이스에 의존적인 어플을 만들수가 있다. 또한 MongoDB를 실행할 수 있게 해준다.

npm i realm

 

import Realm from 'realm'

realm을 import 하면 model을 정의해야한다. 

const TaskSchema = {
	name:"Task",
    properties:{
    _id: "int",
    name: "string",
    status: "string?",
    },
    primaryKey:"_id",
}

 

이를 가지고 데이터베이스와 연결해주어야 하는데 데이터베이스를 열기 위해서는 다음 코드를 사용한다.

const realm = await Realm.open(){
  path: "myrealm",
  schema: [TaskSchema]
  });

 

 

다른 데이터를 추가하기 위해서는 schema에 추가하면 된다. 또한 이를 활성화 시키기 위해서 splash screen의 try & catch문을 통해서 시작해주면 된다.

 

다음으로 다른 컴포넌트와 함께 사용하기 위해 useContext hook을 사용할것이다. 

// Context.js
//App에서 그냥 사용하게되면 경고창이 뜬다 보통 createContext는 따로 파일을 둔다.
export const DATAContext = React.createContext();

//App.js

export default function App() {
  SplashScreen.preventAutoHideAsync();
  const [appReady, setAppReady] = useState(false);
  const [realm , setRealm] = useState(null)
  useEffect(() => {
    async function prepare() {
      try {
		const connection = await Realm.open){
		path: "myrealm",
		schema: [TaskSchema]
		});
        setRealm(connection)
        await new Promise((resolve) => setTimeout(resolve, 2000));
      } catch (e) {
        console.warn(e);
      } finally {
        setAppReady(true);
      }
    }
    prepare();
  }, []);
  const onLayoutRootView = useCallback(async () => {
    if (appReady) {
      await SplashScreen.hideAsync();
    }
  }, [appReady]);

  if (!appReady) {
    return null;
  }
  return (
    <View style={{flex: 1}} onLayout={onLayoutRootView}>
      <DATAContext.Provider value={realm}>
        <NavigationContainer>
          <Navigator />
        </NavigationContainer>
      </DATAContext.Provider>
    </View>
  );
}

 

createRealmContext

https://www.mongodb.com/docs/realm/sdk/react-native/quick-start/

2023년 12월 의 공식문서에 따르면 또 다른 방법이 있는데 React Native SDK 문서를 보면 createRealmContext를 사용하는 방법이다. useContext와 사용하는 방식이 비슷한데 설치를 해야하는 것이 하나 더 추가적으로 있다. 

 

install

npm install realm
npm install @realm/react

 

type define

공식문서에서는 정적타입으로 지정해주었다.

// Define your object model
class Profile extends Realm.Object {
  static schema = {
    name: 'Profile',
    properties: {
      _id: 'objectId',
      name: 'string',
    },
    primaryKey: '_id',
  };
}

 

Realm에게 타입알려주기

import React from 'react';
import Realm, {ObjectSchema} from 'realm';
import {createRealmContext} from '@realm/react';

// Define your object model
class Profile extends Realm.Object<Profile> {
  _id!: Realm.BSON.ObjectId;
  name!: string;
  static schema: ObjectSchema = {
    name: 'Profile',
    properties: {
      _id: 'objectId',
      name: 'string',
    },
    primaryKey: '_id',
  };
}
// Create a configuration object
const realmConfig: Realm.Configuration = {
  schema: [Profile],
};

// Create a realm context
const RealmContext = createRealmContext(realmConfig);

export default RealmContext

 

다음과 같이 설정해주면 schema type이 데이터베이스와 연동이 도었고 RealmContext를 전역으로 사용이 가능하다.

 

Write

create의 첫번째는 write하고자 하는 object의 name이다.  그리고 두번째는 그에 해당하는 것들의 값을 넣어주면 끝이다. 

정말 쉽다.

//other.js

const Compname = ({navigation:{goBack})=>{
  const realm = useContext(DATAContext);
  const onClick = () +> {
    realm.write(()=>{
      realm.create("Task",{
        _id: Date.now(),
        name: " state Value ",
        status:  " state Value ",
      })
    })
    //setStateOne(null)
    //setStateOne("")
    goBack() // 이를 이용한다면 state가 저절로 비어져 따로 setState할 필요가 없다.
  }
  return (
    
  )
}

 

createRealmContext으로 Write를 사용하는 경우

const Write = ({navigation: {goBack}}) => {
  const {useRealm} = realmContext;
  const realm = useRealm();
  const [selectEmo, setSelectEmo] = useState(null);
  const [feeling, setFeeling] = useState("");
  const onChangText = (text) => setFeeling(text);
  const onEmoPress = (face) => setSelectEmo(face);
  const onSubmit = () => {
    if (feeling === "" || selectEmo === null) {
      return Alert.alert("please..");
    }
    realm.write(() => {
      realm.create("Feeling", {
        _id: Date.now(),
        emotion: selectEmo,
        message: feeling,
      });
    });
    goBack();
  };
  return (
    <View>

Read

const Home = ({navigation: {navigate}}) => {
  const realm = useContext(DATAContext)
  const [feeling,setFeeling]=useState([])
  
  useEffect(() => {
    const feeling = useRealm.objects("Feeling");
    
    // const anger = feeling.filtered("emotion = '🤬'"); // 필터링도 가능하다.
    
    // 새로운 것을 추가했을 때 그것을 들어주는 함수 어떤 데이터 접근 같은 것을 넣기 위해 함.
	setFeeling(feeling)
    feeling.addListener(()=>{
      console.log( 'detected something')
    });
    return () =>{
      feelings.removeAllListeners()
  }, []);
  return (
    <View>
      <Title>Home</Title>
      <Btn onPress={() => navigate("WRITE")}>
        <Ionicons name="add" color="white" size={30} />
      </Btn>
    </View>
  );
};

createRealmContext으로 Read하는 경우

const Home = ({navigation: {navigate}}) => {
  const {useQuery} = realmContext;
  const feeling = useQuery("Feeling");
  useEffect(() => {
    feeling.addListener(() => {
      console.log("hohoh");
    });
    return () => {
      feeling.removeAllListeners();
    };
  }, []);
  
  //feeling으로 데이터를 출력할수있다.

Sorted(정렬)

const Home = ({navigation: {navigate}}) => {
  const realm = useContext(DATAContext)
  const [feeling,setFeeling]=useState([])
  
  useEffect(() => {
    const feeling = useRealm.objects("Feeling");	
    feeling.addListener((feeling,changes)=>{
      // 첫번째 인자에는 작성한 data들이 담겨져있다.
      /** 두번째 인자에는 array가 있는데 
      deletions insertions modifications newModifications oldModifications가 있다. 
      발생한 일에 대해 더 많은 컨트롤을 할 수 있다. 
      */
    setFeeling(feeling.sorted("_id",true)) // true라면 가장 큰 id에서 작은 순서로 정렬 false는 반대
    });
    return () =>{
      feelings.removeAllListeners()
  }, []);
  return (
    <View>
      <Title>Home</Title>
      <Btn onPress={() => navigate("WRITE")}>
        <Ionicons name="add" color="white" size={30} />
      </Btn>
    </View>
  );
};

 

Delete

지우는 방식은 두가지가 있는데 PrimaryKey를 이용한 삭제 방식과

item을 전체로 보내줘 삭제하는 방식이 있다.

const Home = ({navigation: {navigate}}) => {
  const {useQuery, useRealm} = realmContext;
  const realm = useRealm();
  const feeling = useQuery("Feeling", (feeling) => {
    return feeling.sorted("_id", true);
  });
  
  const onPress = (id) => {
    realm.write(() => {
      const feelRealm = realm.objectForPrimaryKey("Feeling", id);
      realm.delete(feelRealm);
      //realm.delete(id);
    });
  };
  return (
    <View>
      <Title>Home</Title>
      <FlatList
        data={feeling}
        ItemSeparatorComponent={Seperator}
        keyExtractor={(feeling) => feeling._id + ""}
        renderItem={({item}) => (
          <TouchableOpacity onPress={() => onPress(item_id)}> //onPress(item)
            <Record>
              <Emotion>{item.emotion}</Emotion>
              <Message>{item.message}</Message>
            </Record>
          </TouchableOpacity>
        )}
      />

 

Layout Animations

https://reactnative.dev/docs/layoutanimation

Layout을 움직이게 해준다. 수의 변화가 생겨 렌더링이 될때 Animated를 만들지 않고 애니메이션을 진행시킬수 있다.

android 개발자라면 추가적으로 불러와야할 것들이 있는데. 다음과 같다.

import {
  LayoutAnimation,
  Platform,
  UIManager,
  View,
} from 'react-native';

if (
  Platform.OS === 'android' &&
  UIManager.setLayoutAnimationEnabledExperimental
) {
  UIManager.setLayoutAnimationEnabledExperimental(true);
}

 

const Home = ({navigation: {navigate}}) => {
  const {useQuery, useRealm} = realmContext;
  const realm = useRealm();
  const feeling = useQuery("Feeling", (feeling) => {
    return feeling.sorted("_id", true);
  });
  useEffect(() => {
    feeling.addListener((feeling, changes) => {
      LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
      //LayoutAnimation.easeInEaseOut() 도 가능하다
    });
    return () => {
      feeling.removeAllListeners();
    };
  }, []);
  const onPress = (id) => {
    realm.write(() => {
      const feelRealm = realm.objectForPrimaryKey("Feeling", id);
      realm.delete(feelRealm);
    });
  };
728x90
반응형

들어가기 앞서...

npm start 및 개발을 위해 시작을 하는데 터미널에서 watchmanResponse가

 error: 'std::__1::system_error: open:/ 파일경로 : Operation not permitted'

와 같이 나온다면 

$ watchman watch-del-all
$ watchman shutdown-server

터미널에 입력해주자

원인은 macOS의 보안 제한 사항으로 watchman이 디스크 엑세스 권한을 받지 못한 상태이다.

입력후 다시 실행시켜 권한요청을 확인눌러준다.

Animated

Animated를 import를 하고 원하는 값을 상수로 선언해준다. animate에 사용할 value는 state에 들어가면 안된다.

const Y = new Animated.Value(0);

절대 직접적으로 값을 변경할 수 없다.  예를 들어  

 

let Y = new Animated.Value(0);
y = 20

절대 불가 절대 금지

값은 Animate API를 통해서만 변경 할수 있다.

 

또한 Animate에는 아무것에나 animation을 줄수 없다.  가능한 것은 다음과 같다

Animated.Image
Animated.ScrollView
Animated.Text
Animated.View
Animated.FlatList
Animated.SectionList

또한 위에 없는 것을 사용하고 싶다면 createAnimatedComponent()를 사용해야한다. 

 

만약 styled-components와 함께 사용하고 싶다면 다음과 같다.

const Box = styled(Animated.createAnimatedComponent(View))`
 // 스타일링
`;

또한 다음과 같은 방법도 있다.

const Box = styled.View`
//스타일링
`;
const AnimatedBox = Animated.createAnimatedComponent(Box);

 

Modify Animated Value

다음 세가지가 animated 의  값을 변경할 수 있다.

초기 속도로 시작해서 점점 느려지고 멈춤
Animated.decay()

물리 모델을 제공함.
Animated.spring()

대부분의 경우 timing을 사용함
Animated.timing()

 

Animated.timing() / spring() / decay()

()의 값에는 두가지가 들어가야한다. 하나는 value 다른 하나는 configuration이다.

configuration에서 보낼 수 있는건 첫째로 toValue가 있다.

toValue는 value로 부터 어디까지 가는지를 정하는것이다. 

useNativeDriver는  animation에 대한 모든 데이터와 관련된것이 시작하기 전에 animation에 대한 모든 것을 native 쪽으로 보내준다. 이것은 꼭 true로 값을 해두자

 

spring()에는 bounciness / speed / tension / friction이 들어간다. 또한 bounciness와 speed를 같이 사용할 수 있고 tension과 friction이 같이 사용가능하다.

 

timing()에는 duration, easing, delay, isInteraction도 있다.

Animated Active

Animated.timing().start()
Animated.timing().reset()
Animated.timing().stop()

 

BIG TIP

TouchableOpacity나 Highlight component를 직접적으로 움직이는 것을 되도록 삼가하는게 좋다. 이들은 자체적인 animate가 있기에 animation이 부자연스럽게 작동한다. 

 

new Animated.Value를 사용하게 되면 애니메이션이 활성화면서 Value값으로 돌아오게 되는 이슈를 가질수있게 될것이다. 이럴때 다시 렌더링이 일어나더라도 value를 유지 하게 해주는 hook인 useRef를 사용해주는게 좋다.

const Y = useRef(new Animated.Value(0)).current

 

Interpolation

Y의 값을 input으로 가지고 다른 값으로 변환하는것이다.

예를 들어 [-10 , 10]으로 움직이눈 물체가 있다고 가정하자. 우리는 가운데 0의 값 , 즉, [-10, 0, 10] 값 중 0에 크기를 키운다던지 투명도를 준다든지 따위의 값으로 [1,0,1] 바꿔주는것이다. 

interpolate는 두개의 옵션을 받는다. 

하나는 inputRange이다. 기본적으로 어떤 값들을 가져올것인지 뜻한다.

다른 하나는 outputRange 이다. 가져온 값을 다른 값으로 반환해주는 것.

또한 두값의 길이는 같아야한다.

const AnimatedBox = Animated.createAnimatedComponent(Box);

export default function App() {
  const [toggle, setToggle] = useState(false);
  const Y = useRef(new Animated.Value(200)).current;
  const toggleClick = () => setToggle((prev) => !prev);
  const moveUp = () => {
    Animated.timing(Y, {
      toValue: toggle ? -200 : 200,
      useNativeDriver: true,
      easing: Easing.inOut(Easing.cubic),
    }).start(toggleClick);
  };
  const opacityValue = Y.interpolate({
    inputRange: [-200, 0, 200],
    outputRange: [1, 0, 1],
  });
  return (
    <Container>
      <TouchableWithoutFeedback onPress={moveUp}>
        <AnimatedBox
          style={{transform: [{translateY: Y}], opacity: opacityValue}}
        />
      </TouchableWithoutFeedback>
    </Container>
  );
}

 

Animated.ValueXY

값을 두개를 지정할 수 있다.

const POSITION = useRef(new Animated.ValueXY({x: 0, y: 300})).current;

Animated.sequence / Animated.loop

  const {width: SCREEN_WIDTH, height: SCREEN_HEIGHT} = Dimensions.get("window");
  const POSITION = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
  const topLeft = Animated.timing(POSITION, {
    toValue: {x: -SCREEN_WIDTH / 2 + 100, y: -SCREEN_HEIGHT / 2 + 100},
    useNativeDriver: true,
  });
  const bottomLeft = Animated.timing(POSITION, {
    toValue: {x: -SCREEN_WIDTH / 2 + 100, y: SCREEN_HEIGHT / 2 - 100},
    useNativeDriver: true,
  });
  const bottomRight = Animated.timing(POSITION, {
    toValue: {x: SCREEN_WIDTH / 2 - 100, y: SCREEN_HEIGHT / 2 - 100},
    useNativeDriver: true,
  });
  const topRight = Animated.timing(POSITION, {
    toValue: {x: SCREEN_WIDTH / 2 - 100, y: -SCREEN_HEIGHT / 2 + 100},
    useNativeDriver: true,
  });
  const moveUp = () => {
    Animated.sequence([topLeft, bottomLeft, bottomRight, topRight]).start();
    /**
     Animated.loop(
      Animated.sequence([topLeft, bottomLeft, bottomRight, topRight])
    ).start();
    */
  };

 

 

DRAG

panResponder API

기본적으로 손가락의 제스쳐나 움직임을 감지할 수 있게 해준다.

panResponder에는 많은 function들이 있다. 그 기능들은 panHandlers 안에 묶여져있다. 그중 가장 중요한 function은 onStartShouldSetPanResponder이다. 만약 true를 리턴해주면 이벤트가 지정된 곳에서 터치를 감지하기 시작할 것이다.

 

onPanResponderMove에는 event와 gesture 인자가 있는데 그중 gesture 안에는 여러가지가 내포되어 있는데 가로와 세로축의 움직임을 감지하는 dx , dy 가 있다.    *** (x으로 부터 d 만큼 y로부터 d만큼 이동했다.)

 

onPanResponderRelease 는 손가락을 떼면 동작하는 기능

export default function App() {
  const {width: SCREEN_WIDTH, height: SCREEN_HEIGHT} = Dimensions.get("window");
  const POSITION = useRef(
    new Animated.ValueXY({
      x: 0,
      y: 0,
    })
  ).current;

  const rotation = POSITION.y.interpolate({
    inputRange: [-200, 200],
    outputRange: ["-360deg", "360deg"],
  });
  const borderRadius = POSITION.y.interpolate({
    inputRange: [-200, 0, 200],
    outputRange: [100, 0, 100],
  });
  const bgColor = POSITION.y.interpolate({
    inputRange: [-200, 200],
    outputRange: ["rgb(255, 99, 71)", "rgb(71, 166, 255)"],
  });

  const panResponder = useRef(
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onPanResponderMove: (_, {dx, dy}) => {
        POSITION.setValue({
          x: dx,
          y: dy,
        });
      },
    })
  ).current;
  return (
    <Container>
      <AnimatedBox
        {...panResponder.panHandlers}
        style={{
          transform: [...POSITION.getTranslateTransform()],
          backgroundColor: bgColor,
          borderRadius,
        }}
      />
    </Container>
  );
}

 

offset

opPanResponderMove를 사용해 POSITION의 값을 변경이 가능하게 되어 움직임이게 되면  클릭할 때 중앙으로 점프하는 모습을 볼수가 있다. 마지막 위치를 기억해주기 위해  onPanResponderGrant를 사용해준다.

  onPanResponderGrant: () => {
        POSITION.setOffset({
          x: POSITION.x._value,
          y: POSITION.y._value,
        });
      },

onPanResponderGrant 이 함수는 움직임이 시작될 때 부터 호출되어 터치를 놓을 때까지 추적하고 손가락을 놓으면 자리를 기억한다.

값은 ._value라고 작성해야 값이 넘어간다. 하지만 onPanResponderGrant을 계속 사용하게 되면 값이 계속 더해져서 움직이는 물체가 화면밖으로 나가게 된다. 이때 손가락을 놓을 때 그자리를 0으로 만들어주는 함수를 작성해야한다.

 onPanResponderRelease: () => {
        POSITION.flattenOffset();
      },
728x90
반응형

Refreshing 할 때 IOS는 살짝 위아래로 움직이는 현상 또는 android는 loading indicator가 여러번 나오는 것에 대해..

react query를 사용해서 여러개의(하나는 상관이 없음) usequery의 isRefetching을 통해 boolean을 넘겨주는 구현을 했을때 Refresh를 했을 때 살짝 위아래로 움직이는 현상이 있다. 여려개의 데이터를 가져올때 components들도 다시 랜더링이 되기 때문이다.

이를 해결하기위해 useState와 useQueryClient를 사용해서 코드를 작성하면 도움이 된다.

const [refresh , setRefresh]=useState(false)

const queryClient = useQueryClient();

const activeRefresh = async () => {
    setRefresh(true);
    await queryClient.refetchQueries(["TV"]);
    setRefresh(false);
  };

 

TextInput에서 글씨변화 감지

  const [sTxt, setSTxt] = useState("");
  const onChangeTxt = (text: string) => setSTxt(text);
  return (
    <Container>
      <SearchBar
        placeholder="Search for Movie or Tv Show"
        placeholderTextColor="grey"
        returnKeyType="search"
        autoCapitalize="sentences"
        onChangeText={onChangeTxt}
      />
    </Container>

 

React-Query로 query보내주기

querykeys를 string으로만 보낼 필요가 없다. 원한다면 array도 보낼수있다. react-query를 사용함으로써 query의 key를 fetcher에 보내줄수 있다.

//Some.tsx
...
const [searchTxt, setSearchTxt] = useState("");
  const {isLoading, data, refetch } = useQuery(["searchMovies", searchTxt], movieApi.search, 
  {
    enabled: false,
  });
...

// api.ts

const api ={
 search: ({queryKey}: any) => { //queryKey는 약속된 인자이다. 이름을 바꿔서는 안된다.
    const [_, query] = queryKey;
    return fetch(
      `${BASE_URL}/search/movie?api_key=${API_KEY}&language=ko&page=1&query=${query}`
    )
      .then((res) => res.json())
      .catch((e) => console.log(e));
  },
}

 

Tab에서 Stack으로..

Tab에서 세부페이지로 옮겨갈때 Stack으로 이동하게 되는데 여기서 Tab에 있던 정보를 Stack으로 보내주는 방법이다.

텝의 하위에 있는 파일에서 TouchableOpacity에 onPress를 눌러 해당 stack의 하위 파일로 가는 코드를 짜주자

const goToDetail = () => {
    navigation.navigate("Stack", {screen: "Detail"})
    };
    
   return
   
   <TouchableOpacity onPress={goToDetail}>

여기서 parameter도 보내줄수 있다. parametet는 어떤 종류로든 보내줄수 있다.

  const goToDetail = () => {
    navigation.navigate("Stack", {
      screen: "Detail",
      params: {
        parameter
      },
    });
  };

 

그리고 해당 컴포넌트에서 props를 확인해주자

const Detail: React.FC = (props) => {
  console.log(props);
  return (
    <DetailCont>
      <DetailTxt></DetailTxt>
    </DetailCont>
  );
};

확인한 props에는 navigation과 우리가 보낸 sreen과 params가 있다.

https://reactnavigation.org/docs/navigation-prop#setoptions

 

Navigation prop reference | React Navigation

Each screen component in your app is provided with the navigation prop automatically. The prop contains various convenience functions that dispatch navigation actions. It looks like this:

reactnavigation.org

 

TIP

"title" in params ? params.title : params.name

title이 param안에 있는지 확인해서 그것이 맞다면 params.title이고 거짓이면 params.name을 반환하라.

 

LinearGradient

https://docs.expo.dev/versions/latest/sdk/linear-gradient/

npx expo install expo-linear-gradient

사용법은 쉽다.

 

Linking API

링크를 열 수 있도록 허가해 준다.

import { Linking } from "react-native";
const openLink = async (videoID: string) => {
    const baseUrl = `https://주소/aa?bb=${videoID}`;
    await Linking.openURL(baseUrl)
  };

 

Expo Web Browser

어플리케이션 내의 브라우저를 열도록 허용해준다.

import * as WebBrowser from 'expo-web-browser';

const openLink = async (videoID: string) => {
    const baseUrl = `https://주소/aa?bb=${videoID}`;
	await WebBrowser.openBrowserAsync(baseUrl)
  };

 

Share API

공유하는 방법

  const shareMedia = async () => {
    await Share.share({
      message: 공유할 문구,
      url: URL공유 //IOS 만 가능,
      title: 메세지의 타이틀 //android만 가능
    });
  };

 

Platform API

어떤 플랫폼을 사용하는지 알수있다

import { Platform } from "react-native";
const isAnroid = Platform.OS === "android";

 

BIG TIP

FlatList를 사용할때 StyledComponent로 만들게 된다면 data prop을 타입도 잘 주었는데 keyExtractor의 item이나 renderItem의 오브젝트들이 undefined로 나올때가 있다 이때 두가지 방법이 있는데

하나는 직접 FlatList를 사용하는 방법

두번째는 const Wrapper = styled.FlatList`` as unknown as typeof FlatList

이렇게 작성하면 타입이 인식이 될것이다.

Infinite Scroll

FlatList의 prop으로 onEndReach와 onEndReachedThreshold 사용해 구현을 할 수 있다.

onEndReach 은 바닥에 닿았을때 함수를 실행시킬수 있다.

onEndReachedThreshold은 onEndReach를 실행시키려는 목록의 하단에서 내용의 끝까지의 거리이다. 0.5라고 값을 주었을때 내용의 끝이 목록의 보이는 길의의 절반 안에 들어왔을 때 onEndReach를 실행시킨다.

 

infinite Quries

#https://tanstack.com/query/v3/docs/react/guides/infinite-queries

useQuery말고 useInfiniteQuery를 사용해서 data를  flat매소드를 통해 Array 속 Array들을 정리해주자

다음으로 fetcher 뒤에 option으로 getNextPageParam사용하는데 여기에는 두개의 인자를 받을 수 있다.

첫째로 우리가 마지막으로 fetch한 페이지지고 두번째는 지금까지 부른 모든 페이들의 배열이다.

또한 page parameter를 갖게 되면 fetch한 함수에 즉시 전달이 된다.

 const {isLoading, data } =
    useInfiniteQuery<IType>(
      [category, key],
      fetch function,
      {
        getNextPageParam: (currentPage) => {
          const nextPage = currentPage.page + 1;
          nextPage > currentPage.total_pages ? null : nextPage;
        },
      }
    );
    
    //api
     getData: ({pageParam}: any) =>
    fetch(
      `${BASE_URL}/aaa/bbb?api_key=${API_KEY}&page=${pageParam}`
    )
      .then((res) => res.json())
      .catch((e) => console.log(e)),
      
      //pageParam이 안될때는 pageParam ?? 1 을 시도 해볼것
728x90
반응형

RN - Stlyed - Components

사용법은 웹과 비슷하지만 불러올때 /native 폴더 지정해주자

 

Adding TypeScript to Existing Project

npm install -D @tsconfig/react-native @types/jest @types/react @types/react-test-renderer typescript

프로젝트에 위 를 설치하고

Root에 tsconfig.json을 만들어

{
  "extends": "@tsconfig/react-native/tsconfig.json"
}

추가 해준다. 후에 나머지 파일을 모두 tsx로 바꿔준다. 제일 중요한 건index.js는 건들지 말자.

 

타입스크립트를 사용하다가도 .jsx를 사용하면 JS를 사용할 수 있다.

후에 styled.d.ts를 사용하기 위해 서는 웹과 방식은 동일하다

 

만약 theme color를 작성해놓은 ts파일이 style이면 바꿔주어야한다. styled.d.ts와 충돌이 일어난다.

 

React Navigation은 이미 가져올 수 있는 많은 Types를 제공해준다. 

 

 

Swiper - React Native Web Swiper 

스와이퍼 컴포넌트를 제공한다.

 

Expo BlurView - npx expo install expo-blur

IOS 개발할 시 npx pod-install 도 해야한다.

설치하고 npm run 을 다시 실행해야한다. native 요소에 접근한 것을 새로 설치한다면 무조건 해야한다.

 

React-Native - StyleSheet.absoluteFill

제일 많이 사용되는 패턴의 css가 제공되어진다.

<View style={StyleSheet.absoluteFill}>
	<Text>텍스트</Text>
</View>

StyleSheet.absoluteFill	은

const ContView = styled.View`
	position:absolute;
	width:100%;
    height:100%
`

같다

 

 

TIP. 날짜 관련

new Date('2021-12-20').toLocaleDateString("ko")
//2021.12.20
new Date('2021-12-20').toLocaleDateString("ko",{month:'long', day:'numeric', year:'long'})
//2021년 12월 20일

 

FlatList

ScrollView는 많은 도움을 주지만 application 퍼포먼스에 좋지 않다.

ScrollView는 모든 자식 컴포넌트를 한번에 render한다는 것이다. 그러니까 화면에 나오지 않는 부분까지 모두 로딩을 하고 렌더링 한다는 것이다. 그러기 위해서 FlatList가 등장한다. 이것은 Lazy하게 만들어준다.

FlatList는 렌더링하기 위한 perfomance interface이다.

또한 SectionList도 있는데 (https://reactnative.dev/docs/sectionlist) 링크에가서 확인하도록 하자 . IOS 어플 개발시 매우 유용할것으로 보임.

 <FlatList
          //FlatList 내부에서 스스로 map과 같이 처리해줌.
          data={trending}
          //renderItem은 function이고 1개의 인자를 받고 component를 반환해주면 된다.
          renderItem={({item}) => (
            <Trending
              backdropPath={item.~~~}
              originalTitle={item.~~~}
              voteAverage={item.~~~}
            />
          )}
        />

여기서 경고가 뜰 수도 있는데 VirtualizedList는 같은 방향의 ScrollView 안에 감싸져 있음 안된다.

FlatList는 Children을 가질수 없다.

수직방향의 FlatList를 수직방향의 ScrollView 안에 넣을 수 없다. 이것을 가능하게 하는것은 ListHeaderComponent 이다.

ListHeaderComponent는 컴포넌트를 모든 아이템 위쪽에 render해버린다.

unmount

우리가 탭을 눌러 컴포넌트를 떠나면 해주는 방법은  Tab.Navi에서 screenOption에 unmountOnBlue:true prop을 작성해주자

 

QueryClient

react query를 사용할때 한번에 refetch를 하는 방법이 있는데 refetch와 isRefetching을 사용해서 하는 방법도 있지만 코드가 길어진다. 그러기 위해서 useQueryClient를 사용해주면 좋다.

queryClient는 제일 큰 단위라고 생각하고 useQuery는 개별단위라 생각하자.

refetchQueries를 하면되는데 여기서 제일 제일 중요한 핵심포인트는 useQuery에서 key를 적어주는 곳에 배열을 만들고 첫번째에 묶어줄 카테고리를 작성하고 그후에 키를 작성해준다. 

const queryClient = useQueryClient();
 const {
    isLoading: nowLoading,
    data: nowData,
    isRefetching: isRefechingNowPlay,
  } = useQuery<IMovieData>(["movies", "nowPlay"], movieApi.getNowPlayingMovie);
  const {
    isLoading: trendLoading,
    data: trendData,
    isRefetching: isRefechingTrend,
  } = useQuery<ITrending>(["movies", "trendMoive"], movieApi.getTrendingMovie);
  const {
    isLoading: upcomeLoading,
    data: upcomeData,
    isRefetching: isRefechingUpcome,
  } = useQuery<IMovieData>(
    ["movies", "upcomeMovie"],
    movieApi.getUpcommingMovie
  );
  const activeRefresh = async () => {
    await queryClient.refetchQueries(["movies"]);
  };

 

728x90
반응형

Detective DarkMode (Theme)

useColorScheme / Apperance

Apperance module은 몇몇 function과 method를 제공해주는데 

 

이번에는 useColorScheme을 활용할 것이다.

 

import {useColorScheme} from "react-native";
function names(){
  const colorScheme = useColorSheme()
  return
}

 

시뮬레이터에서 ios에서는  shift + command + a 조합으로 모드를 바꿀 수 있음

안드로이드는 시뮬레이터 모바일 내에 setting으로 들어가 직접 바꿔주는 방법도 있는데 필자는 헤드 네비게이션을 내려서 퀵버튼을 편집하여 추가하였다. 

import {createBottomTabNavigator} from "@react-navigation/bottom-tabs";
import Movie from "../screens/Movie";
import Search from "../screens/Search";
import Tv from "../screens/Tv";
import {useColorScheme} from "react-native";

const Tab = createBottomTabNavigator();
const Tabs = () => {
  const isDark = useColorScheme() === "dark";
  return (
    <Tab.Navigator
      initialRouteName="Search"
      screenOptions={{
        headerTitleAlign: "center",
        tabBarStyle: {backgroundColor: isDark ? "#164863" : "#DDF2FD"},
        tabBarActiveTintColor: isDark ? "#DDF2FD" : "#FFC436",
        tabBarInactiveTintColor: isDark ? "#427D9D" : "#164863",
        headerStyle: {
          backgroundColor: isDark ? "#164863" : "#DDF2FD",
        },
        headerTitleStyle: {color: isDark ? "#FFC436" : "#427D9D"},
      }}
    >
      <Tab.Screen name="Movies" component={Movie}></Tab.Screen>
      <Tab.Screen name="Search" component={Search}></Tab.Screen>
      <Tab.Screen name="TV" component={Tv}></Tab.Screen>
    </Tab.Navigator>
  );
};
export default Tabs;

 

 

하지만 색상을 고려하는것에 많으 스트레스가 있다면 Theme에 대해 배워보자 @react-navigation/active에서 DarkTheme /  DefaultTheme

  <NavigationContainer
      onReady={onLayoutRootView}
      theme={isDark ? DarkTheme : DefaultTheme}
    >
      <Tabs />
    </NavigationContainer>

Tab Nav / ICON

한가지 방법으로는 tabBarIcon을 사용하는 것이다. screenOptions로 navigator에 두거나 screen에도 둘수 있는데

navigator에 두면 함수로 보내주어야하고 screen으로는 object로 보내줄수 있다.

  <Tab.Navigator
      initialRouteName="Search"
      screenOptions={{
        headerTitleAlign: "center",
        tabBarStyle: {backgroundColor: isDark ? "#164863" : "#DDF2FD"},
        tabBarActiveTintColor: isDark ? "#FFC436" : "#164863",
        tabBarInactiveTintColor: isDark ? "#427D9D" : "#164863",
        headerStyle: {
          backgroundColor: isDark ? "#164863" : "#DDF2FD",
        },
        headerTitleStyle: {color: isDark ? "#FFC436" : "#427D9D"},
      }}
    >
      <Tab.Screen
        name="Movies"
        component={Movie}
        options={{
          tabBarIcon: ({focused, color, size}) => {
            return <Ionicons name="film-outline" size={size} color={color} />;
          },
        }}
      ></Tab.Screen>
      <Tab.Screen
        name="Search"
        component={Search}
        options={{
          tabBarIcon: ({focused, color, size}) => {
            return <Ionicons name="search-outline" size={size} color={color} />;
          },
        }}
      ></Tab.Screen>
      <Tab.Screen
        name="TV"
        component={Tv}
        options={{
          tabBarIcon: ({focused, color, size}) => {
            return <Ionicons name="tv-outline" size={size} color={color} />;
          },
        }}
      ></Tab.Screen>
    </Tab.Navigator>

 

Stack Navigator

Stack Navigator는 새 screen이 이전의 screen 위로 올라오는 것이다.

두가지 옵션이 있는데 stack과 native stack 이 있다. 

stack navigator는 React Navigation에 있는 것이고. 자바스크립트로 구현된것이다. 익숙한 ios나 안드로이드의 모습으로 가지고 있다.

이게 중유한 이유는 각 Platform의 navigation을 사용하지 않는다는 것이다. 이것을 사용하면 커스텀이 가능하다.

 

native stack navigator는 native API를 이용해 구현된 것이다.

createNativeStackNavigator를 사용하여 navigator를 만들면 native로 만든 일반적인 어플리케이션과 완전히 같은 방식으로 작동하고, 퍼포먼스도 똑같다고 한다. native stack navigator를 사용하게 되면 커스텀 할 수 있는 영역이 약간 줄어들것이다. 왜냐면 이것은 ios와 안드로이드의 navigator를 사용하는것이기 때문이다.

npm install @react-navigation/native-stack
import {createNativeStackNavigator} from "@react-navigation/native-stack";
import {Text, TouchableOpacity, View} from "react-native";

const ScreenOne = ({navigation: {navigate}}) => (
  <View>
    <TouchableOpacity onPress={() => navigate("two")}>
      <Text>screen one</Text>
    </TouchableOpacity>
  </View>
);
const ScreenTwo = ({navigation: {navigate}}) => (
  <View>
    <TouchableOpacity onPress={() => navigate("three")}>
      <Text>screen two</Text>
    </TouchableOpacity>
  </View>
);
const ScreenThree = ({navigation: {goBack, setOptions}}) => (
  <View>
    <TouchableOpacity onPress={() => setOptions({title: "hihi"})}>
      <Text>Touch me</Text>
    </TouchableOpacity>
    <TouchableOpacity onPress={() => goBack()}>
      <Text>go Back</Text>
    </TouchableOpacity>
  </View>
);
const NativeStack = createNativeStackNavigator();

const Stack = () => {
  return (
    <NativeStack.Navigator>
      <NativeStack.Screen name="one" component={ScreenOne}></NativeStack.Screen>
      <NativeStack.Screen name="two" component={ScreenTwo}></NativeStack.Screen>
      <NativeStack.Screen
        name="three"
        component={ScreenThree}
      ></NativeStack.Screen>
    </NativeStack.Navigator>
  );
};
export default Stack;


//app.js
 <NavigationContainer onReady={onLayoutRootView}>
      <Stack />
    </NavigationContainer>
import {createNativeStackNavigator} from "@react-navigation/native-stack";
import {Text, TouchableOpacity, View} from "react-native";

const ScreenOne = ({navigation: {navigate}}) => (
  <View>
    <TouchableOpacity onPress={() => navigate("two")}>
      <Text>screen one</Text>
    </TouchableOpacity>
  </View>
);
const ScreenTwo = ({navigation: {navigate}}) => (
  <View>
    <TouchableOpacity onPress={() => navigate("three")}>
      <Text>screen two</Text>
    </TouchableOpacity>
  </View>
);
const ScreenThree = ({navigation: {goBack, setOptions}}) => (
  <View>
    <TouchableOpacity onPress={() => setOptions({title: "hihi"})}>
      <Text>Touch me</Text>
    </TouchableOpacity>
    <TouchableOpacity onPress={() => goBack()}>
      <Text>go Back</Text>
    </TouchableOpacity>
  </View>
);
const NativeStack = createNativeStackNavigator();

const Stack = () => {
  return (
    <NativeStack.Navigator
      screenOptions={{
        headerBackTitleVisible: false,
        headerTintColor: "pink",
        presentation: "formSheet",
        animation: "fade",
      }}
    >
      <NativeStack.Screen
        name="one"
        component={ScreenOne}
        options={{title: "1"}}
      ></NativeStack.Screen>
      <NativeStack.Screen
        name="two"
        component={ScreenTwo}
        options={{title: "2"}}
      ></NativeStack.Screen>
      <NativeStack.Screen
        name="three"
        component={ScreenThree}
        options={{presentation: "modal"}}
      ></NativeStack.Screen>
    </NativeStack.Navigator>
  );
};
export default Stack;

 

Combine Stacks & Tabs

solution .1

//app.js
    <NavigationContainer onReady={onLayoutRootView}>
      <Tabs />
    </NavigationContainer>
    
//Tabs.js
<Tab.Screen
        name="Movies"
        component={Stack}
        options={{
          headerShown: false,
          tabBarIcon: ({focused, color, size}) => {
            return (
              <Ionicons
                name={focused ? "film" : "film-outline"}
                size={size}
                color={color}
              />
            );
          },
        }}
      ></Tab.Screen>

Solution.2 

//app.js
    <NavigationContainer onReady={onLayoutRootView}>
      <RootNav /> //=> 새로 만든다.
    </NavigationContainer>
    
// RootNav.js
    <Navigate.Navigator screenOptions={{headerShown: false}}>
      <Navigate.Screen name="Tabs" component={Tabs} />
      <Navigate.Screen name="Stack" component={Stack} />
    </Navigate.Navigator>
    
// Movie.js
const Movie = ({navigation: {navigate}}) => (
  <TouchableOpacity
    onPress={() => navigate("Stack", {screen: "three"})} //=> 내가 갈곳으로 이벤트를 준다.
    style={{flex: 1, justifyContent: "center", alignItems: "center"}}
  >
    <Text>Movie</Text>
  </TouchableOpacity>
);

//Bottom Tab Nav
하단에 내가 갈 큰 카테고리
// Stack Nav
그 중간 카테고리

// 폴더를 구성한다고 생각하면 이해가 쉬울것 같음.
728x90
반응형

React Navigation

React Navigation은 React Native에서 navigation을 만드는데 독보적이다.

npm install @react-navigation/native
npx expo install react-native-screens react-native-safe-area-context

 

npx pod-install ios

아래 코드들을 android/app/src/main/java/<your package name>/MainActivity.java. 에 추가

create-react-native-app으로 프로젝트를 생성했으면 추가할 필요가 없다.

public class MainActivity extends ReactActivity {
  // ...
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(null);
  }
  // ...
}

 

 

import android.os.Bundle;

 

Tab Navigation

bottom-tabs navigation

(https://reactnavigation.org/docs/bottom-tab-navigator)

npm install @react-navigation/bottom-tabs
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

const Tab = createBottomTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator>
    // 모든 스크린에는 이름이 있다.또한 컴포넌트도 있다.
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

 

import * as SplashScreen from "expo-splash-screen";
import {useFonts} from "expo-font";
import {Asset, useAssets} from "expo-asset";
import {Ionicons} from "@expo/vector-icons";
import {StatusBar} from "expo-status-bar";
import {useCallback, useEffect, useState} from "react";
import {Image, StyleSheet, Text, View} from "react-native";
import {NavigationContainer} from "@react-navigation/native";
import Tabs from "./navigation/Tabs";

export default function App() {
  const [ready, setReady] = useState(false);
  const [assets] = useAssets([
    require("./sky.jpg"),
    "https://blog.kakaocdn.net/dna/chdzI0/btqWU8aSuLk/AAAAAAAAAAAAAAAAAAAAAIfEz8Xsn-ZgISvRievor1l4l4_xl2Wkn0wbLCoBIKe-/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1756652399&allow_ip=&allow_referer=&signature=WLlYDyvC%2BVWb2F9f4GyZPsqZrHk%3D",
  ]);
  const [fontLoaded] = useFonts(Ionicons.font);
  useEffect(() => {
    async function prepare() {
      try {
        await SplashScreen.preventAutoHideAsync();
        await new Promise((resolve) => setTimeout(resolve, 3000));
      } catch (e) {
        console.warn(e);
      } finally {
        setReady(true);
      }
    }
    prepare();
  }, []);
  const onLayoutRootView = useCallback(async () => {
    if (ready) {
      await SplashScreen.hideAsync();
    }
  }, [ready]);

  if (!ready || !assets || !fontLoaded) {
    return null;
  }
  return (
    <NavigationContainer onReady={onLayoutRootView}>
      <Tabs />
    </NavigationContainer>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});

 

Tab.Navigation Props

initialRouteName

initialRouteName은 첫번째로 렌더링될 route이다.

 

screenOptions

options으로는 많은 것들이 있다.

많은 것들을 option으로 만들려면 screenOption을 Tab.Navigator안에 사용해야한다. 

각각의 Screen에게는 options prop을 사용하면 된다.

const Tabs = () => (
  <Tab.Navigator
    initialRouteName="Search"
    screenOptions={{tabBarLabelStyle: {backgroundColor: "tomato"}}}
  >
    <Tab.Screen name="Movies" component={Movie}></Tab.Screen>
    <Tab.Screen
      name="Search"
      component={Search}
      options={{tabBarLabelStyle: {color: "green", fontSize: 20}}}
    ></Tab.Screen>
    <Tab.Screen name="TV" component={Tv}></Tab.Screen>
  </Tab.Navigator>
);
export default Tabs;

 

tabBarBadge

확인 안한 메세지 개수 뱃지 라고 생각하면 쉬움 문자열, boolean, null 도 반환됨

const Tabs = () => (
  <Tab.Navigator initialRouteName="Search">
    <Tab.Screen name="Movies" component={Movie}></Tab.Screen>
    <Tab.Screen
      name="Search"
      component={Search}
      options={{tabBarBadge: 5}}
    ></Tab.Screen>
    <Tab.Screen name="TV" component={Tv}></Tab.Screen>
  </Tab.Navigator>
);

tabBarActiveTintColor / tabBarInactiveTintColor

눌렀을때 활성화 색 / 비활성화 되었을 때 색

const Tabs = () => (
  <Tab.Navigator initialRouteName="Search">
    <Tab.Screen name="Movies" component={Movie}></Tab.Screen>
    <Tab.Screen
      name="Search"
      component={Search}
      options={{tabBarBadge: null, tabBarActiveTintColor: "#24aa31"}}
    ></Tab.Screen>
    <Tab.Screen name="TV" component={Tv}></Tab.Screen>
  </Tab.Navigator>
);
728x90
반응형

React-Native CLI를 설치하기 위한 자료는 (https://reactnative.dev/docs/environment-setup?guide=native&platform=android&os=macos) 에 나와있다.

 

React Native CLI로 작업하기 위해서는 <개발하는 OS>와 <타겟되는 OS>를 정해야한다. 필자의 같은 경우에는 <개발 OS>는 <Mac OS> // <타겟 OS>는 Android 로 정하고 개발환경을 만들어주었다. 

 

1. Node & Watchman 설치

brew install node
brew install watchman

 

2. Java Develope Kit (JDK)

brew tap homebrew/cask-versions
brew install --cask zulu11

# Get path to where cask was installed to double-click installer
brew info --cask zulu11

 

환경변수 설정 (zshrc)

터미널에
vim ~/.zshrc 입력

e 눌러 edit mode
page down 눌러 제일 아래로 이동후 
insert 활성하고 아래 작성

export JAVA_HOME="/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home"
export PATH=${PATH}:$JAVA_HOME/bin

작성후 ctrl+c 누르고 :wq 눌러 vim~/.zshrc에서 나오기

터미널 종료 후 다시 시작하여

echo $JAVA_HOME

입력후 경로 확인

 

3. Android 개발 환경 설정

  • android sdk
  • android sdk platform
  • android virtual device

설치

  • 3-1. Android SDK 설치 

2023년 11월 16일 기준 Android 13(Tiramisu) 설치를 권고함

Android Studio 열고 More Action에서 SDK MANAGER로 들어가서 설치

  • Android SDK Platform 33
  • Intel x86 Atom_64 System Image또는 Google APIs Intel x86 Atom System Image또는 (Apple M1 Silicon의 경우)Google APIs ARM 64 v8a System Image

 

  • 3-2 Android_HOME 환경 변수 구성
터미널에
vim ~/.zshrc 입력

e 눌러 edit mode
page down 눌러 제일 아래로 이동후 
insert 활성하고 아래 작성

export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools

작성후 ctrl+c 누르고 :wq 눌러 vim~/.zshrc에서 나오기

 

4. Project 설치

원하는 폴더 만들고 vscode 열어 아래 명령어 입력

npx react-native@latest init AwesomeProject

 

5. 프로젝트 시작

npm run android
혹은 
npx react-native run-android

 

 

설치 중 오류처리

Project 설치시 Ruby의 버전이 낮아 Cocoapods를 설치를 못하는 경우가 있었다.

Ruby의 버전을 업데이트하기 위한 사용한 방법을 나열하겠다.

brew update
brew upgrade

brew install rbenv ruby-build
// 위 설치 후 rbenv로 설치할 수 있는 Ruby버전을 확인한다.

rbenv install -l 
// ruby 홈페이지가서 스텐다드 버전을 확인하고 다운 받았다.

rbenv install 3.x.x
// 설치 후 설치된 버전으로 바꾸기 위해

rbenv global 3.x.x 
//입력

ruby --version
//위를 입력하여 본인이 설치한 버전이 바뀌었는지 확인한다. 
// 터미널을 껏다 켜고 다시 한번 더 확인한다.

버전이 바뀌지 않았을 때

rbenv init

// 를 입력하면

# Load rbenv automatically by appending
# the following to ~/.zshrc:

eval "$(rbenv init - zsh)"

//라고 나오는데 이것을

vim ~/.zshrc

// 위를 입력하여 편집 모드로 들어가 제일 하단에 

eval "$(rbenv init - zsh)"

// 위의 문구를 추가해주자
// 추가 후 터미널을 껏다 다시 켜고
// ruby --version을 눌러준다.

 

Watchman Error

npm run android를 하고 watchman 터미널이 따로 등장하는데 등장과 함께 에러코드가 나왔다.

...내용중략~~~ watchman error operation not permitted ~~~~내용중략...

 

문구가 나왔을 시 순차적으로 입력하자.

watchman watch-del-all
watchman shutdown-server

 

또한 .watchmanconfig 파일 내부는 

{}

중괄호가 입력되어 있어져야한다.

728x90

+ Recent posts