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
반응형
728x90

 

위와 같은 UI는 사용자 경험에 도움이 되기 때문에

많은 상용앱에서 볼수있다.

 

이는 아이폰에서도 x계열에서 특별한 처리가 필요한데

하단에 여백이 발생하기 때문이다.

 

어떻게 처리할수있는지 살펴보자.

개인적으로는 꽤나 시행착오를 걸쳤다.

 

키워드는 3가지이다.

1. KeyboardAvoidingView

2. SafeAreaView

3. react-native-iphone-x-helper

 

3번 라이브러리는 설치를 해주도록하자.

npm i react-native-iphone-x-helper --save

 

이제 구현을 할건데

KeyboardAvoidingView는 항상

제일 껍데기에 둔다고 생각하면 편하다.

 

Docs에서 보면

안드로이드와 ios에 동작 방식을 다루게 주기도 하는데

여기서는 동일하게 처리하도록 한다.

 

import { KeyboardAvoidingView, SafeAreaView, View, StatusBar, Platform } from 'react-native';
import { isIhponeX } from 'react-native-iphone-x-helper';


const statusBarHeight = StatusBar.currentHeight;
const isIphone = isIphoneX() && Platform.OS === 'ios';
const paddingBottom = isIphone ? 34 : 0;
const offset = isIphone ? -34 : statusBarHeight;

...

return (
<KeyboardAvoidingView
  behavior='height'
  style={{ flex: 1, backgroundColor: '#00cebe', paddingBottom }}
  keyboardVerticalOffset={}
>
  <SafeAreaView style={{ backgroundColor: '#000000' }}>
    <View style={{ flex: 1 }}>
      ...
      <CustomInput 
        keyboardType='number-pad'
        placeholder='인증 코드를 입력해주세요'
      />
    </View>
    <CustomButton>다음</CustomButton>
  </SafeAreaView>
</KeyboardAvoidingView>
)

 

input이나 button은 만들기 나름인데

버튼은 아래와 같이 만들었다.

const BottomButtonContainer = styled.TouchableWithoutFeedback`
  width: 100%;
  height: 56px;
`;

const BottomButtonWrapper = styled.View`
  width: 100%;
  height: 56px;

  align-items: center;
  justify-content: center;
  background-color: #00cebe;
`;

const BottomButtonText = styled.Text`
  color: white;
  text-align: center;
  font-size: 16px;
  line-height: 30px;
  font-weight: bold;
  letter-spacing: -0.48px;
`;

export const BottomButton = ({ name, onPress }: { name: string; onPress: Function | any; }) => (
  <BottomButtonContainer onPress={onPress}>
    <BottomButtonWrapper>
      <BottomButtonText>{name}</BottomButtonText>
    </BottomButtonWrapper>
  </BottomButtonContainer>
);

 

 

 

**************************

**** 21. 11. 30 업데이트 ****

**************************

 

문제상황

behavior를 height로 잡았더니

TextInput의 focus나 스크롤 위치 등

상황이 변화함에따라 버튼이

키보드 아래로 숨어버리거나

반만 보이거나 하는 등의 문제상황이 발견되었다.

 

따라서,

아래와 같이 수정하였으며

UX적인 측면에서 훌륭하게 동작함을 확인하였다.

// app.ts

import { StatusBar } from 'react-native';

... 생략
return (
  <>
    <StatusBar 
      barStyle='light-content'
      backgroundColor: 'black'
    />
  </>
)
// auth.code.ts

import { KeyboardAvoidingView, SafeAreaView, View, StatusBar, Platform } from 'react-native';
import { isIhponeX, getStatusBarHeight } from 'react-native-iphone-x-helper';


...(생략)
const _inputValiation = ():boolean = () => {
  let valid = true;
  ...(생략)
 
  return valid;
}

const isIphonexStyle = isIhponeX() && Platform.OS === 'ios';
const statusBarHeight = isIphonexStyle ? getStatusBarHeight(true) +3 : StatusBar.currentHeight;

return (
<KeyboardAvoidingView
  behavior={Platform.OS === 'ios' ? 'padding' : undefined}
  style={{ 
    flex: 1, 
    backgroundColor: _inputValiation() ? 'red' : 'gray', 
    marginTop: isIphonexStyle ? statusBarHeight : 0
  }}
>
  <SafeAreaView style={isIphonexStyle ? { backgroundColor: 'transparent' } : {}}>
    <ScrollView style={{ flex: 1, backgroundColor: 'black' }} keyboardShouldPersistTaps='never'>
      ...(생략)
      
      <CustomInput 
        keyboardType='number-pad'
        placeholder='인증 코드를 입력해주세요'
      />
    </ScrollView>
    <CustomButton style={{ backgroundColor: _inputValiation() ? 'red' : 'gray' }}>다음</CustomButton>
  </SafeAreaView>
</KeyboardAvoidingView>
)

 

핵심은 behavior 부분의 변화이다.

안드로이드에서는 AndroidManifest.xml에

...

<activity 
  ...
  android:windowSoftInputMode="adjustResize" <-- 여기
>
...
</activity>

위의 한줄만 있으면 keyboardavoidingview에서

따로 처리를 안해도 적절하게 동작한다.

 

따라서 android에서는 undefined를.

ios에서는 권장사양이자 적절하게 동작하는

padding으로 설정을 해주었다.

 

나머지는 약간의 트릭을 통해

버튼이나 배경색 등을 컨트롤하는 부분이다.

728x90
반응형
728x90
npm i --save react-native-fast-image
cd ios && pod install && cd ..

Image 라이브러리를 설치한다.

 

ios는 그냥 해도 잘 렌더링되지만

android는 간단한 세팅을 해줘야한다.

/android/app/bulild.gradle 

...
  implementation 'com.facebook.fresco:fresco:2.0.0'
  implementation 'com.facebook.fresco:animated-gif:2.5.0'
...

 

앱 실행(재실행)

import FastImage from 'react-native-fast-image';

...
<FastImage
  style={{ width: 100, height: 100 }}
  source={{ uri: 'https://mygifimage.com/test.gif' }}
/>
...
728x90
반응형
728x90

앱 배포를 눈앞에 두고 디테일을 잡는중

앱 실행되고 처음 새 화면으로 이동할때

순간적으로 하얗게 화면이 깜빡이는것이 목격됐다.

 

react의 Painting작업중 이슈가 생긴듯하다.

어떻게 처리할까 고민하다가

약간의 꼼수로 하얗게 깜빡이는 이슈를

잠재우기로했다.

 

// app.js

<NavigationContainer theme={{ colors: { background: '#000000' } }}>
 ...
</NavigationContainer>

Navigation의 기본 배경색을 전환될 화면의 배경색과 통일해 줌으로써

하얗게 되는 이슈를 해결했다. (?)

728x90
반응형
728x90

[ 푸시알림 완벽구현 - 최종판 ]

https://honeystorage.tistory.com/306

 

 

@react-native-firebase/app

@react-native-firebase/messaging

 

두개 모듈을 이용해 푸시알림을 구현했다.

ios & android 모두 잘 동작하는 듯 했으나 아니었다

 

Docs를 보다보면 푸시알림 수신관련해

상태를 구분해둔 표가있다.

 

그렇다는건

우리도 상태별로 테스트를

진행해서 문제가 없는지 확인해야된다는 것이다.

 

나는 이 중

Android 디바이스의 앱이 Quit Stats 상태일때

정상적으로 알림을 띄우지 못했다.

 

1박2일간의 사투끝에 어이없는 실수가 있었음을 깨달았다.

문제 해결 과정이 핵심이다.

 

1. Google에 문제 상황 검색하기

- 검색어 예시 : react-native-firebase push notification not working on android in quit stats

- 검색어 예시 : - rnfb not working in quit stats

 

2. git에 예시코드 찾아보기

- 검색어 예시 : react-native-firebase v6

- 검색어 예시 : rn firebase

 

3. 잠시 멈추어, 정확히 어느상황에 안되는지 확인

- 생각을 정리해보니 JS단까지 알림이 전달되않음.

- Java에는 알림이 전달이 되는지 확인해볼 필요가 있음

 

4. 디바이스를 연결하고 Android Studio로 디버깅

- 디바이스 연결

- Android Studio 실행

- react-native run-android

- Android Studio 하단의 LogCat [info]를 모니터링

- 서버에서 알림 발송

- Java에는 요청이 정확히 오지만 에러가 났음을 발견

- 에러메시지 >>> SoLoader.init ....

- MainApplication.java에 해당 코드를 주석처리 해뒀던것이 기억남

- 주석해제

 

5. 푸시알림 전송 및 이슈해결 확인

 

 

결론

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false); // <-- 주석해제했음
  }

 

 

>>> RNFirebase 왜 아이콘이 안나와?

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

 

 

[ 푸시알림 완벽구현 - 최종판 ]

https://honeystorage.tistory.com/306

728x90
반응형
728x90

IOS를 테스트할때

Firebase의 App Distribution을 이용하거나 TestFlight를 이용하듯이

 

Play Console에도 내부테스트 기능이 존재한다.

그 옛날 직접 apk파일을 공유할때와 비교하면

정말 편리해졌다.

 

방법도 간단했다.

 

1. 빌드한다.

cd android
./gradlew bundleRelease

2. play console에 앱을 등록한다.

3. 테스트 > 내부테스트로 이동한다.

4. 빌드해서 얻은 파일을 등록한다.

(빌드된 파일 위치: app/build/outputs/bundle/release/app-release.aab)

5. 기타 빌드 관련 사항을 기록하고 내부테스트 시작

6. 테스터를 지정한다.

7. 링크를 복사해 테스터에게 공유한다.

8. 테스터는 플레이스토어앱을 연다.

9. 프로필 > 설정 > 정보 > 스토어버전 연타!

(개발자 설정 완료 뜨면)

프로필 > 설정 > 일반 > 내부앱  공유 On

10. 이메일에 테스터 지정에 동의한다.

11. 공유받은 링크를 클릭해 PlayStore로 이동

12. 앱을 설치한다.

728x90
반응형
728x90

attribute중에 contenteditable을 사용하면

div를 input, textarea처럼 쓸수있다.

 

입력창을 커스텀해서쓸때 종종 쓰이곤한다.

방법은 간단하다.

 

html + css / html + scss 버전 두가지를 살펴보자

 

1. html + css

<div contenteditable placeholder='메시지를 입력해주세요'></div>
[placeholder]:empty:before {
  display: block;
  content: attr(placeholder);
  color: #a6a6a6;
}

 

 

2. html + scss

<div contenteditable placeholder='메시지를 입력해주세요'></div>
&[placeholder]:empty:before {
  display: block;
  content: attrs(placeholder);
  color: #a6a6a6;
}

 

 

728x90
반응형