728x90

패키지를 추가하거나

native코드를 수정하는 등의

큰 변화를 주지않고

 

js, img, css 를 수정하는 배포에대해

빌드를 하지 않고도 배포할수 있게 해주는

code-push기능 적용 일기이다.

 

참고 링크 1 Weiji 웨이지 님의 블로그

참고 링크 2 Gale Lee 님의 블로그

참고링크 3 appcenter 공식 문서

본 작업은 ,  react-native 0.66 버전에서 진행되었습니다.

 

1. 설치

npm i -g appcenter-cli

 

2. 로그인

appcenter login

위 명령어를 입력하면

? Enable Temporary(Y/n)? 라는 질문이 나타난다.

원투데이 개발할게 아니므로 당연히 n

 

그러고나면 terminal에

?Access code from brwoser이라는 질문과함께

연동된 페이지가 나타나며

구글/깃헙/페북 등 로그인 수단을 선택할것이라고 뜬다.

 

나는 github을 선택했고, username을 설정하고나니

code가 떴다.

코드 복붙

그러면 로그인 완료

 

3. 로그인 확인

appcenter profile list

위 명령어로 로그인된 계정의

Username, Display Name, Email 등을 확인할 수 있다.

 

4. 서비스 등록하기 (대소문자 주의)

appcenter apps create -d appname-aos -o Android -p React-Native
appcenter apps create -d appname-ios -o iOS -p React-Native

이름은 자유지만 android/ios 구분해서 짓기

 

5. 등록된 서비스 조회

appcenter apps list

 

6. 홈페이지 접속해보기

https://appcenter.ms/apps

 

7. react-native-code-push 설치하기

npm i --save react-native-code-push

 

8.  배포단계 설정하기

appcenter codepush deployment add -a username/appname-aos Staging
appcenter codepush deployment add -a username/appname-aos Production
appcenter codepush deployment add -a username/appname-ios Staging
appcenter codepush deployment add -a username/appname-ios Production

이렇게 배포단계에 대한 네이밍을 세팅해두면

appcenter codepush deployment list -a username/appname-aos

이런 방식으로 현재 진행중인 상태를 조회할수 있다고 한다.

 

9. 앱의 배포를 위해 key 조회하기

appcenter codepush deployment list -a username/appname-aos -k
appcenter codepush deployment list -a username/appname-ios -k

 

10. ios 설정

- 라이브러리를 설치했으니 pod도 설치해준다.

cd ios && pod install && cd ..

 

- xcode의 AppDelegate.m 수정

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
  #if DEBUG
    return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  #else
    return [CodePush bundleURL];
  #endif <-- 새로 변경된 파트
  
//#if DEBUG
//  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
//#else
//  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
//#endif <-- 원래 파트
}

 

- app에 AppCenter-Config.plist 추가

NewFile -> Property List -> Save as (AppCenter-Config.plist)

 

- Project > info > Configuration > +

위 이미지처럼 Staging항목을 추가해줍니다.

 

- Info.plist에 Key 추가하기

key: CodePushDeploymentKey / value: ${CODEPUSH_KEY}

를 추가해줍니다.

 

Project > Build Settings > + Add user defined setting을 클릭

 

MULTI_DEPLOYMENT_CONFIG 추가

release $(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

staging $(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME)

 

같은 방법으로

CODEPUSH_KEY 추가

appcenter codepush deployment list -a username/appname-ios -k

위 명령어로 조회된 키를 

release, staging에 알맞게 입력

 

 

11. android 설정

- android/setting.gradle

// add code push
include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')

 

- key 추가하기

먼저, 아래 명령어로 키를 조회

appcenter codepush deployment list -a username/appname-aos -k

아래 내용 추가

// android/gradle.properties

CODEPUSH_DEPLOYMENT_KEY_DEBUG=
CODEPUSH_DEPLOYMENT_KEY_STAGING=????
CODEPUSH_DEPLOYMENT_KEY_PRODUCTION=????

 

- /android/app/build.gradle

// buildTypes
buildTypes {
  debug {
    ...
    resValue "string", "CodePushDeploymentKey", CODEPUSH_DEPLOYMENT_KEY_DEBUG
  }
  release {
    ...
    resValue "string", "CodePushDeploymentKey", CODEPUSH_DEPLOYMENT_KEY_PRODUCTION
  }
  releaseStaging {
    initWith release
    resValue "string", "CodePushDeploymentKey", CODEPUSH_DEPLOYMENT_KEY_STAGING
    matchingFallbacks = ['release']
  }
}




// enableHermes 관련 코드가 있다면
if (enableHermes) {
  ...
  releaseStagingImplementation files(hermesPath + "hermes-release.aar")
} else {
  ... 
}



// 맨 아래에 추가
apply from: "../../node_modules/react-native/react.gradle" <-- 에러나면 주석처리
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"

- MainApplication.java

import com.microsoft.codepush.react.CodePush; // <- add

new ReactNativeHost(this) {
  ...
  
  //add
  @Override
  protected String getJSBundleFile() {
    return CodePush.getJSBundleFile();
  }
}

 

+++ 안드로이드에서 코드푸시가 되지않아 에러 추적을해본 결과

[CodePush] Exception com.microsoft.codepush.react.CodePushUnknownException: Error in getting binary resources modified time

위와 같은 에러가 발견됨

// android/app/build.gradle

defaultConfig {
  ...
  resValue 'string', "CODE_PUSH_APK_BUILD_TIME", String.format("\"%d\"", System.currentTimeMillis())
}

이 방법으로 해결함

 

설정할게 많다...

 

그치만 우리가 얻는 이득에 비하면 이건 아무것도아니다.

(하, 그래도 이거는 솔직히 할거 너무많다)

 

설정은 끝났다.

업데이트를 수신할건지에 대한 코드만 추가해주면된다.

 

12. 코드 추가하기

(여기서부터는 자유롭게 하면 될것같다)

// 필자는 App.tsx 에서 작업하였다.

import CodePush from 'react-native-code-push';
import { AppState } from 'react-native';

useEffect(() => {
  const staging = AppState.addEventListener('change', (state) => {
    if (state === 'active') codePushSync();
  });
  
  return () => {
    staging.remove();
  }
}, [])

const codePushSync = () => {
  CodePush.sync({
    updateDialog: {
      title: '새로운 업데이트가 존재합니다.',
      optionalUpdateMessage: '지금 업데이트 하시겠습니까?',
      optionalIgnoreButtonLabel: '나중에',
      optionalInstallButtonLabel: '업데이트'
    },
    installMode: CodePush.InstallMode.IMMEDIATE
  })
}



export default CodePush({ checkFreQuency: CodePush.CheckFrequency.MANUAL })(App);

 

빌드파일을 지우고

re-빌드해서 배포테스트를 해보자.

728x90
반응형
728x90

개발중

안드로이드에서 Modal을 닫을때

순간적으로 깜빡이는 이슈가

별견되었다.

 

github. Issue에

많은 솔루션들이 있었지만

 

나는 아래의 방법을 적용하니

깔끔하게 해결되었다.

 

return (
  <Modal 
    style={Style}
    isVisible={visible} 
    animationIn={InEffect}
    animationOut={OutEffect}
    backdropOpacity={Opacity}
    backdropTransitionOutTiming={0} <-- 여기
  >
    <ModalContainer>{Viewer}</ModalContainer>
  </Modal>
);

 

참고링크:

https://github.com/react-native-modal/react-native-modal/issues/268

 

 

추가로 이런 이슈도 있었다.

대부분의 modal라이브러리가 그렇듯

react-naive-modal에도 visible 프로퍼티가 존재한다.

 

반드시 modal의 노출 여부는

이 visible로만 핸들링 해야한다.

아래와 같은 코드에서는 비정상적인 움직임을 보여준다.

 

// 올바른 사례
const [show, setShow] = useState(false);

<Modal
  visible={show}
>
</Modal>



// 잘못된 사례
const [show, setShow] = useState(false);
{
  !!show &&
  <Modal
    visible={show}
  >
  </Modal> 
}

 

어떤 이유에선가 아래와 같이 작업할지도 모른다.

그렇게하면 on/off는 컨트롤 할 수 있겠지만

비정상적인 애니메이션을 보게 될것이다.

728x90
반응형
728x90

이전 글

[1. 객체지향 프로그램의 의미를 알아보자]

https://honeystorage.tistory.com/287

 

아래 내용 관련 Github :)

https://github.com/jaekwangLee/oop_js_study

 

우리는 이전 글에서

객체지향의 탄생에는

절차지향 프로그래밍의 한계가 있음을

알게되었다.

 

이제는 구체적으로

객체지향의 특징들을 알아보자.

 

흔히 4가지 특징이 있다고 한다.

1. 캡슐화 : 내부의 특성은 안으로 숨기고 사용자에게는 사용법만을 노출시킨다.

2. 상속 : 부모의 특성을 자식이 물려받음

3. 다형성 : 하나의 클래스나 메소드가 다양한 방식으로

4. 추상화 : 중요한 정보만을 표현하여 공통의 속성이나 이름을 묶어 이름 붙임

 

한글로만 봐도 어려운 단어들이지만

하나씩 이해해보자

실제로 써보면

절차지향 프로그래밍에서는 못느꼈던

새로운 희열을 느낄수있다.

 

다시, 이전의 예시코드를 살펴보자.

이 짧은 예시코드안에

객체지향 특성을 모두 볼 수 있다.

{
  interface NormalCar {
    start(): void;
    stop(): void;
    run(): void;
    spec: Specifications;
    ownerName: string;
  }

  interface SuperCar {
    start(): void;
    stop(): void;
    run(): void;
    boost(): void;
    spec: Specifications;
    ownerName: string;
  }

  type Specifications = {
    tires: number;
    fuel: number;
    owner: string;
  };

  type OnOff = "off" | "on";

  class Vehicle {
    private engine: OnOff = "off";
    constructor(tires: number, fuel: number) {
      console.log(tires, fuel);
    }

    start = (): void => {
      console.log("start");
      this.engineToggle("on");
    };

    stop = (): void => {
      console.log("stop");
      this.engineToggle("off");
    };

    run = (): void => {
      console.log("keep going...");
    };

    boost = (): void => {
      console.log("speed up!!!");
    };

    get status() {
      return "car status : " + this.engine;
    }

    private engineToggle = (nextStatus: OnOff): void => {
      this.engine = nextStatus;
    };
  }

  class Car extends Vehicle {
    constructor(private tires: number, private fuel: number, private owner: string) {
      super(tires, fuel);
    }
    
    get spec() {
      return {
        tires: this.tires,
        fuel: this.fuel,
        owner: this.owner,
      };
    }
    
    get ownerName() {
      return this.owner;
    }
    
    set ownerName(newName: string) {
      this.owner = newName;
    }
  }

  // car 인스턴스 생성
  const myCar = new Car(4, 50, "steve");
  myCar.start();
  console.log(myCar.status);
  
  myCar.stop();
  console.log(myCar.status);

  // car 인스턴스 생성할 때 interface를 이용해 추상화
  const myFamilyCar: NormalCar = new Car(4, 60, "steve_family");
  // myFamilyCar.boost() <-- error

  const myHobbyCar: SuperCar = new Car(4, 48, "steve_hobby");
  myHobbyCar.boost();
}

1. 캡슐화

캡슐화는 ownerName을 통해 알수있다.

사용자는 ownerName을 읽거나 쓸수 있지만

class내에서는 owner라는 이름으로 존재한다.

 

사용자는 직접 owner를 수정하거나

읽어들일 수 없다.

 

마찬가지로 engine의 상태 또한

직접 변경할수 없으며,

시동을 걸거나(start) 끔으로써(stop)

상태를 변경할 수 있다.

 

이렇게 변수를 은닉함으로써

불필요하게 class내 멤버변수에 접근하여

값을 변경하는 경우를 방지할 수 있다.

 

2.  상속

Vehicle -> Car

위와 같은 방식으로 상속이 진행되었다.

 

Vehicle이라는 큰 카테고리 내에서 

공통의 속성이나 이름을 묶어주었다.

(모든 탈것에는 공통적으로  tires, fuel, start, stop, run 등의 기능이 존재함)

 

Car에는 Vehicle이 갖는 특성 외에

항상 주인이 정해지게 된다.

 

따라서, vehicle을 상속함과 동시에

생성자함수(constructor)에서 owner를 지정하게 해주었다.

 

다음, 제품별로 고유의 특성들을 또 갖게될텐대

porche 같은 고성능 차에는 boost라는 기능이 추가된다거나

주유구 방향 등을 설정할수도 있겠다.

 

3. 추상화

추상화에는 두가지 방법이 있다.

접근제한자 (private, public, protected)를 통한 추상화와

interface를 통한 추상화가 있다.

 

추상화의 목적은 사용성의 향상에 있다.

예를들어, 추상화처리를 하지 않았다면

Car를 통해 생성한 인스턴스를 사용하고자 할때

start,stop,run,boost,engine,owner,fuel,tires ...

등등 불필요한 properties를 보게될것이다.

 

개발자가 제공하고자하는 항목만을

추상화를 통해 제공할 수 있다.

 

위에서도 private으로 정보 접근을 제한함과 동시에

불필요한 옵션을 줄였고,

interface를 통해 Normal Car에서는 boost라는

property를 제공하지 않게 설정하였다.

 

3. 다형성

부모의 특성을 재정의하는

"오버라이딩"을 통해 이루어지게되는데,

자식들이 부모의 특성을 그대로 물려받기도 하지만

종종 변화를 주는 경우가 생긴다.

 

만약, 절차지향 프로그래밍이었다면

부분적으로 다르게 동작하는 자동차를 만들려면

대부분의 코드를 중복해서 사용하고

해당 부분을 수정해서 새로운 함수를 만들어야 할것이다.

 

객체지향 프로그래밍에서는

오버라이딩을 통해 원하는 부분만 수정한

새로운 객체를 쉽게 생성해낼수있다.

 

이를 다형성이라고 한다.

728x90
반응형
728x90

좀 더 나은 개발자가 되기위해

나를 되돌아보고

무엇을 다시 혹은 더 공부해야 될지

고민하던중

 

내가 OOP를 제대로 못하고있다고

생각이 들었다.

 

아직도 OOP를 제대로 못한다니

부끄럽지만 숨기지않고

인정하고 공부했을때

나아갈수 있는것이다.

 


아래 내용 관련 Github :)

https://github.com/jaekwangLee/oop_js_study

 

 

그러면 가장 기본적인

객체지향 프로그래밍의 의미를 알아본다.

 

시중에서 접하는 책들에서는

자동차, 커피머신, 로봇 등을 예시로 든다.

 

그 전에

더 이전부터 존재했던 개념인

절차지향 프로그래밍의 의미와

단점에 대해서 알아야

왜 객체지향이 등장했는지,

객체지향을 적용해야만 하는 이유를

알수있다.

 

절차지향 프로그래밍이 뭘까?

말그대로 절차를 중요시하며

순차적으로 실행되는 프로그래밍 기법을 말한다.

 

예를들어, 자동차가 있다면

시동 (start) 을 구현한다고 가정해보자.

절차지향적인 프로그래밍에서는 어떻게 할까?

// car.js
function car() {
  const fuel = 50;
  const brand = 'porche';
  const owner = 'steve';
  const tires = 4;
  
  function stop() {
    1. 사용자가 시동을 끈다.
    2. 액션을 엔진에 전달한다.
    3. 엔진이 신호를 받는다.
    4. 엔진이 동작을 멈춘다..
  }

  function start() {
    1. 사용자가 시동을 건다
    2. 액션을 엔진에 전달한다.
    3. 엔진이 신호를 받는다.
    4. 엔진이 동작을 시작한다.
  }
}
...

 (역시 개발자는 코드 구조로 봐야 이해가 쉽다)

 

이렇게 개발을 하면 단점이 뭘까?

무슨 문제가 있기에 객체지향을 해야하는 걸까?

 

1. 유지보수

누군가 저렇게 개발해둔 코드를 유지보수 하게 됐다면

지옥을 맛볼것이다.

시동 부분의 일부를 수정하는데도

이해해야 하는 코드가 너무나도 방대한것이다.

 

자동차 관련 코드 전체를 읽어야지

수정이 가능한 상황이 발생하기 쉽상이다.

 

2. 재사용성

start라는 시동 액션이 여기서만 쓰일까?

1) 개발중인 자동차와 다 똑같은데,

시동걸때 "시동!" 이라고 음성을 들려주는 자동차를 만들려면

수없이 중복되는 코드들을 짜야될것이다.

 

2) 자동차가 아닌 오토바이의 시동을 개발하게 되었다면,

자동차의 시동부와 액션이 같을 확률이 높지만 우리는

수없이 중복되는 코드들을 짜야될것이다.

 


 

이런 문제들을 알고

객체지향에 대해 다시 알아보자.

 

흔히 말하는 객체지향의 장점은

아래의 코드로 확인할 수 있다.

{
  interface NormalCar {
    start(): void;
    stop(): void;
    run(): void;
    spec: Specifications;
    ownerName: string;
  }

  interface SuperCar {
    start(): void;
    stop(): void;
    run(): void;
    boost(): void;
    spec: Specifications;
    ownerName: string;
  }

  type Specifications = {
    tires: number;
    fuel: number;
    owner: string;
  };

  type OnOff = "off" | "on";

  class Vehicle {
    private engine: OnOff = "off";
    constructor(tires: number, fuel: number) {
      console.log(tires, fuel);
    }

    start = (): void => {
      console.log("start");
      this.engineToggle("on");
    };

    stop = (): void => {
      console.log("stop");
      this.engineToggle("off");
    };

    run = (): void => {
      console.log("keep going...");
    };

    boost = (): void => {
      console.log("speed up!!!");
    };

    get status() {
      return "car status : " + this.engine;
    }

    private engineToggle = (nextStatus: OnOff): void => {
      this.engine = nextStatus;
    };
  }

  class Car extends Vehicle {
    constructor(private tires: number, private fuel: number, private owner: string) {
      super(tires, fuel);
    }
    
    get spec() {
      return {
        tires: this.tires,
        fuel: this.fuel,
        owner: this.owner,
      };
    }
    
    get ownerName() {
      return this.owner;
    }
    
    set ownerName(newName: string) {
      this.owner = newName;
    }
  }

  // car 인스턴스 생성
  const myCar = new Car(4, 50, "steve");
  myCar.start();
  console.log(myCar.status);
  
  myCar.stop();
  console.log(myCar.status);

  // car 인스턴스 생성할 때 interface를 이용해 추상화
  const myFamilyCar: NormalCar = new Car(4, 60, "steve_family");
  // myFamilyCar.boost() <-- error

  const myHobbyCar: SuperCar = new Car(4, 48, "steve_hobby");
  myHobbyCar.boost();
}

 

다음 장에서

위 코드에 대한 분석과

객체지향의 장점에 대해

이어서 공부해보도록 하자.

728x90
반응형
728x90

나는 그동안 redux를 쓰며 의문을 가졌다.

사실 쓰기 매우 꺼려지기까지 했다.

 

global state 관리를 위해

redux를 쓰는것은 동의

 

다만, 그 많은 코드를 써가면서까지

비동기 요청을 하고

응답된 데이터를 state로 관리하고

해야되는걸까?

 

실제로 서비스를 개발하면서

gloal state로 관리해야될 데이터는

많지 않았던것같다.

 

때에 따라서는

global state로 관리되는 데이터들에대한

최신화 문제를 겪기도했다.

 

이번에 새로운 앱 MVP 제작을위해

자료조사를 하던도중

이 포스팅을 보고

"react-query" 를 도입해보았다.

 

react-query는 global state를

server-state와 client-state라는

두개의 개념으로 분리해서 바라본다.

 

나는 이에 크게 동의했다.

 

예를들어, 포스팅 목록을 불러왔을때는

글로벌로 관리해야한다고본다.

(사용자가 새로고침하기 전까지는)

 

대신, 포스팅 단일 데이터는

굳이 글로벌로 관리할 필요가 있을까?

아니라고 생각한다.

 

global state라는 개념을 도입하게된 배경은

[부모 -> 자식 -> 자식 ->  자식 -> 자식]

으로 props를 전달하는데 불편함이 있어서이며,

불필요한 fetch를 방지하기 위함이다.

 

포스팅 단일 데이터호출 및 렌더링에는

이런 문제가 있지도 않을텐대

그 많은 코드를 써가며 global state로

관리할 이유가 없다고 생각하는것이다.

 

이런 측면에서 접근했을때

경우에 따라 global state로 관리해야 하는 경우에만

Redux를 이용하고

일반적으로는 state 그 자체면 충분하다.

 

특히나, 비동기 요청을 위해

redux-sage, redux-thunk등을 쓸 이유?

는 없어진다고 생각한다.

 

아직까지는 대규모 프로젝트에서

관리의 용이성을 위해 redux + redux-saga를 많이 쓴다곤 하지만

react-query가 어느정도 궤도에 이르면

redux + react-query 구조로 가지는 않을까하고

조심스럽게 예상해본다.

 

이제 만들고있는 앱에

redux도 붙이러 가야겠다

 


 

redux-saga, redux-thunk가

너무나도 싫어서

기피하고 외면하던 게으른 개발자가

 

마음에 쏙드는 신문물을 발견해

작성한 포스팅입니다.

728x90
반응형
728x90

pm2를 이용해서

앱서버를 백그라운드 환경으로 운영이 가능하다.

 

여기서 한발 더 나아가

cluster mode로 실행중인 node.js서버를

pm2를 이용해 돌리곤 했었다.

 

하지만 배포할때마다 일시적으로 중단되어버리는 서버.

원인은 restart로 배포하기때문.

 

reload 명령어를 통해

kill -> restart

reset 형식으로 서버 중단없이

무중단 배포가 가능하다.

pm2 reload index.js

 

근데 이를 제대로 활용하려면

몇가지 설정이 필요하다

 


 

pm2 ecosystem

 

위 명령어로 ecosystem.config.js 파일을 얻을수 있다.

다양한 설정들이 기본으로 세팅되어있는데

docs를 보면서 입맛에 맞게 설정하면 된다.

 

진짜 간단히는

module.exports = {
    apps: [
        {
            name: 'server',
            script: './index.js',
            watch: '.',
            instances: -1, // 클러스터 모드
            // exec_mode: 'cluster', <-- 이것도 클러스터 모드
        },
    ],
};

이렇게만 설정해도 된다

 

package.json을 수정하면

npm 명령어로 서버에서

간편히 실행 혹은 reload 하는것도 가능하다.

scripts: {
  "start": "pm2 start ./ecosystem.config.js --only server",
  "reload": "pm2 reload server"
}

 

고객에게 불편을 주지말자

 


여기서 더 나아간다면

실행중인 process가 덮어씌워지는 경우를

방지하고

reload할 수 있다.

 

// server.js

const app = express();


let disableKeepAlive = false;

// 중단이 감지되면 Keep 상태인 요청 닫음
app.use((req, res, next) => {
    if (disableKeepAlive) {
        res.set('Connection', 'close');
    }
    next();
});

const service = app.listen(process.env.PORT, () => {
    console.log(`The application is listening on port ${process.env.PORT}`);
    if (process.send) {
        console.log('send')
        process.send('ready');
    }
});

process.on('SIGINT', async () => {
    disableKeepAlive = true;
    service.close();
    process.exit(0);
});

이런식으로 구성해주면 된다.

728x90
반응형
728x90

>>> RNFirebase 백그라운드 알림이 왜 수신이 안돼?

>>> RNFirebase 왜 진동이 안울려?

 

 

[Android에 해당하는 내용]

 

따로 설정하지 않으면

notification icon으로

기본 앱 icon으로 설정된다는 내용부터

 

transparent 이미지만

사용 가능하다는 등

다양한 솔루션을 거쳐

 

결론적으로 어떻게해야

아이콘을 display 할수있는지

알아냈다.

 

먼저, 아래 링크에서

이미지를 규격별로 만들것이다.

링크: 이미지 만들기

 

다음으로는, 여기

공식 가이드라인에서 안내하는데로

firebase.json을 수정해줄것이다.

 

그전에, /android/src/main/res/values/colors.xml에 

나의 앱에 맞는 custom-color를 만들고오자.

<resources>
  <color name='white'>#ffffff</color>
  <color name='custom'>#eeeee</color>
</resources>

 

다시 돌아가

firebase.json에 아래 링크를 참조해서

추가 설정을 해본다.

참고자료: 링크

// <projectRoot>/firebase.json
{
  "react-native": {
    "messaging_android_notification_color": "@color/custom"
  }
}

 

설정은 반쯤 되었다.

이번에는

아까 만든 규격별 이미지를 

/android/src/main/res/

아래에 모두 넣어주자.

이런느낌?

 

마지막으로

AndroidManifest.xml를 수정하고

실행해보면

정상적으로 notification 아이콘이 뜨는걸 볼수있다.

<application
 ...
>
  ...
  <meta-data
    android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@mipmap/이미지명" 
  />
  <meta-data
    android:name="com.google.firebase.messaging.default_notification_color"
    android:resource="@color/custom"
  /> <-- color 지정할때 반드시 firebase.json에서 설정한것과 같은 값으로 할것
  ...
</application>

 

실행 혹은 빌드한뒤

푸시를 보내보자

정상적으로 뜬다면 행복해하면된다.

 

>>> RNFirebase 백그라운드 알림이 왜 수신이 안돼?

>>> RNFirebase 왜 진동이 안울려?

728x90
반응형
728x90

수치의 최소, 최대치를 정하기위한

UI적 요소로 Multi slider를 고려하곤 한다.

(Multi slider란 이런식으로 양방향 슬라이딩을 하는것을 말함)

 

많은 라이브러리를 검토해본 결과

아래의 라이브러리를 선정하였으며

관련하여 짤막한 정리를 하고자한다.

 

npm i @ptomasroos/react-native-multi-slider@2.2.2

설치때는 2.2.2 버전으로 받기를 권장한다.

최신 버전에서는 오류를 경험했었다.

github의 issue를 트랙킹하여

2.2.2에서는 문제 없이 돌릴수 있음을 확인하였다.

 

Docs에 예제코드는 물론 자세한 설명이 나와있지만

나의 예제 코드를 본다면

<MultiSlider
  values={values}
  min={20}
  max={100}
  step={20}
  containerStyle={{alignItems: 'center', zIndex: 5}}
  trackStyle={{backgroundColor: 'blue', height: 2.5}}
  selectedStyle={{backgroundColor: 'red'}}
  sliderLength={screenWidth - 52}
  snapped
  customMarker={() => (
    <FastImage style={{width: 28, height: 28}} source={image} />
  )}
  onValuesChange={updateValue}
/>

 

참고로 위 이미지에서

칸칸이 표시한것은 별도의 컴포넌트를

슬라이더 뒤에 깔아준것이다.

 

728x90
반응형
728x90

구현간에 어처구니없는 이슈로

삽질을 1시간이나해서 남기는 기록...

 

목표

1. animation 처리할 박스의 크기를 구한다.

2. 박스 크기 측정이 완료되면 Collapsible 느낌으로 나타나게한다.

* 조건: 처리할 박스 하단에는 다른 컴포넌트는 없음

 

어떻게 처리할지 고민이 되었다.

박스의 크기를 구하려면

이미 박스가 렌더링 되어있어야하는데

그러면 Collapsible 느낌을 낼수가 없다는 역설

 

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc() {
  return (
    <>
      <View>
        { children } <-- 이 내용물이 Collapsible 느낌으로 나타나는게 목표이다
      </View>
    </>
  )
}

Higher Order Component를 만들어 원할때

언제든지 쓰게할것이다

따라서 이름을 AnimateHoc라고 지어보았다.

 

그럼 박스의 크기를 일단 구해본다.

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [_height, setHeight] = useState(0);
  
  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  }

  return (
    <>
      <View onLayout={onLayout}> <-- 박스크기 측정
        { children }
      </View>
    </>
  )
}

 

이러면 쉽게 박스의 크기를 구할수있는데,

이것이 사용자에게 노출되면 안된다.

꼼수로 opacity로 투명도를 낮춰주자.

 

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [_height, setHeight] = useState(0);
  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  }

  return (
    <>
      <View onLayout={onLayout} style={{ opacity: 0 }}> <-- 꼼수
        { children } <-- 이 내용물이 Collapsible 느낌으로 나타나는게 목표이다
      </View>
    </>
  )
}

투명도를 낮추더라도 공간은 차지하니

아 아래에 다른 무언가가 있다면 이 HOC는

쓰기에 적합하지 않다. 다른 방법을 찾아야한다.

아니면 약간의 수정을

 

 

다음은, children이 서서히 나타나며 보이게하는 부분이다

(마치, Collapsible 처럼)

 

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [init, setInit] = useState(false);
  const [_height, setHeight] = useState(0);
  const viewHeight = useSharedValue(0);
  
  useEffect(() => {
    if (!init && _height > 0) {
      viewHeight.value = _height;
      setInit(true); <-- 굳이 init이라는 장치를 한번 더 적용한 이유는 잘못된 리렌더링을 방지하기 위함
    }
  }, [_height])

  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  } 
  
  const fadeDown = useAnimatedStyle(() => {
    return {
      height: withTiming(viewHeight.value, {
        duration: duration || 750
      }),
    }
  }, [init])

  return (
    <>
      <Animated.View <-- 서서히 나타나야될 부분
        style={[{ height: 0 }, fadeDown]}
      >
        { children }
      </Animated.View>
      
      { <-- 크기 측정을 위해서 opacity: 0 상태로 렌더링하다가 측정완료되면 렌더링 하지않음
        !_height &&
        <View onLayout={onLayout} style={{ opacity: 0 }}> <-- 꼼수
          { children }
        </View>
      }
    </>
  )
}

 

이제는 완벽하다 생각했다

문제없이 동작할것이라 생각했는데

duration이 없기라도한듯

한번에 animated view가 나타났다

 

왜지, 아무리 구글링을해도

Docs를 살펴봐도

라이브러리 사용면에서는 문제는 없어보였다.

 

그래서 animated view에  backgroundColor를 넣어서

정상적으로 서서히 크기가 커지나 살펴보니

아주 정상적이었다!

다만, 그 내용물이 한번에 나타났을뿐!

박스는 천천히, 내용물은 한번에...

 

그래서overflow hidden을 설정해줌으로써 완벽히 정리되었다.

최종 코드

import React, { ReactNode, useEffect, useState } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from 'react-native-reanimated';

function AnimateHoc({ duration, children }) {
  const [init, setInit] = useState(false);
  const [_height, setHeight] = useState(0);
  const viewHeight = useSharedValue(0);
  
  useEffect(() => {
    if (!init && _height > 0) {
      viewHeight.value = _height;
      setInit(true);
    }
  }, [_height])

  const onLayout = e => {
    const { height } = e.nativeEvent.layout;
    setHeight(height);
  } 
  
  const fadeDown = useAnimatedStyle(() => {
    return {
      height: withTiming(viewHeight.value, {
        duration: duration || 750
      }),
    }
  }, [init])

  return (
    <>
      <Animated.View <-- overflow 멈춰!!!
        style={[{ height: 0, overflow: 'hidden' }, fadeDown]}
      >
        { children }
      </Animated.View>
      
      {
        !_height &&
        <View onLayout={onLayout} style={{ opacity: 0 }}>
          { children }
        </View>
      }
    </>
  )
}

 

이제 이걸 활용한다면 이렇게 할수있을것이다.

// 기본 사용법
return (
  <View>
    <View>
     ... 
    </View>
    <AnimatedHoc>
      <View>
        <Text>1. 커져라 커져!</Text>
      </View>
      <View>
        <Text>2. 커져라 커져!</Text>
      </View>
      <View>
        <Text>3. 커져라 커져!</Text>
      </View>
    </AnimatedHoc>
  </View>
)

// 시간 조절 사용법
return (
  <View>
    <View>
     ... 
    </View>
    <AnimatedHoc duration={ 1500 }>
      <View>
        <Text>1. 천천히 커져라 커져!</Text>
      </View>
      <View>
        <Text>2. 천천히 커져라 커져!</Text>
      </View>
      <View>
        <Text>3. 천천히 커져라 커져!</Text>
      </View>
    </AnimatedHoc>
  </View>
)
728x90
반응형
728x90

1. 설치 관련

Docs에 따르면 앱을 설치한 뒤

babel.config.js를 수정해주어야 한다.

이 때 잘못 설정하면 에러가 발생한다.

module.exports = {
    presets: ['module:metro-react-native-babel-preset'],
    plugins: [
        'babel-plugin-styled-components',
        [
            'module-resolver',
            {
                root: ['.'],
                extensions: [
                    '.ios.ts',
                    '.android.ts',
                    '.ts',
                    '.ios.tsx',
                    '.android.tsx',
                    '.tsx',
                    '.jsx',
                    '.js',
                    '.json',
                ],
                alias: {
                    '@components': './src/components',
                    '@utils': './src/utils',
                    '@interface': './src/interface',
                    '@stack': './src/stack',
                    '@api': './src/api',
                    '@containers': './src/containers',
                    '@libs': './src/libs',
                    '@screens': './src/screens',
                    '@images': './src/images',
                },
            },
            // 'react-native-reanimated/plugin', <-- 여기 아님, 절대 아님
        ],
        'react-native-reanimated/plugin',
    ],
};

추가시 위치에 조심하자

특히, 반드시 맨 뒤에 삽입해야한다.

 

 

2. Freezing 현상

설치하고나니 랜덤하게 앱이 멈춰버리는 현상이 나타났다.

이슈를 뒤져보니 Freezing 관련한 글이 있었다.

https://github.com/software-mansion/react-native-reanimated/issues/1875

 

해결을 위해서는 

설치된 버전을 제거하고

react-native-reanimated@2.2.3 버전을 설치해주어야한다.

이 에러는 react-native-modal이나 react-navigation등의 라이브러리들과

충돌로인해 발생한다고한다.

728x90
반응형