728x90
반응형

https://www.npmjs.com/package/react-native-lottie-splash-screen

 

react-native-lottie-splash-screen

A lottie splash screen for react-native, hide when application loaded ,it works on iOS and Android.. Latest version: 1.1.2, last published: 3 months ago. Start using react-native-lottie-splash-screen in your project by running `npm i react-native-lottie-sp

www.npmjs.com

 

해당 npm package 를 적용할때, ios에서 몇가지 이벤트가 있어서 글을 작성한다.

 

안드로이드같은경우는, 해당 안내에 따라 한번에 적용이 되었는데,

 

IOS 같은 경우는 자동으로 헤더가 생기지 않아서 어려움을 겪었다.

 

최종 프로젝트 트리는

이다. xcode 에서 Dynamic.swift를 추가해줘야 자동으로 코드가 생긴다.

 

Dynamic.swfit 코드는

 

import UIKit
import Foundation
import Lottie

@objc class Dynamic: NSObject {

@objc func createAnimationView(rootView: UIView, lottieName: String) -> LottieAnimationView {
let animationView = LottieAnimationView(name: lottieName)
animationView.frame = rootView.frame
animationView.center = rootView.center
animationView.backgroundColor = UIColor.white;
return animationView;
}

@objc func play(animationView: LottieAnimationView) {
animationView.play(
completion: { (success) in
RNSplashScreen.setAnimationFinished(true)
}
);
}
}

로, 기존 AnimationView를 LottieAnimationView로 바꿔줘야한다는 npm 개발자의 코멘트가 있었다.

 

AppDelegate.mm 의 코드중 일부는

 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([FIRApp defaultApp] == nil) {
[FIRApp configure];
}
 .
 .
 .

// return [super application:application didFinishLaunchingWithOptions:launchOptions];
BOOL success = [super application:application didFinishLaunchingWithOptions:launchOptions];
 
if (success) {
//This is where we will put the logic to get access to rootview
UIView *rootView = self.window.rootViewController.view;
 
rootView.backgroundColor = [UIColor whiteColor]; // change with your desired backgroundColor
 
Dynamic *t = [Dynamic new];
UIView *animationUIView = (UIView *)[t createAnimationViewWithRootView:rootView lottieName:@"loading"]; // change lottieName to your lottie files name
 
// register LottieSplashScreen to RNSplashScreen
[RNSplashScreen showLottieSplash:animationUIView inRootView:rootView];
// casting UIView type to AnimationView type
LottieAnimationView *animationView = (LottieAnimationView *) animationUIView;
// play
[t playWithAnimationView:animationView];
// If you want the animation layout to be forced to remove when hide is called, use this code
[RNSplashScreen setAnimationFinished:true];
}
 
return success;
 
}

로 수정되어야한다. rn 0.71버전 상위 버전에 바뀌어야하는 코드와 AnimationView를 LottieAnimationView 로 변환시켜줘야한다.

lottieName도 해당 json파일로 변경 시켜줘야한다.

 

 

728x90
반응형
728x90
반응형

https://www.npmjs.com/package/react-native-signup-checkbox

 

react-native-signup-checkbox

checkbox list when signup for react native. Latest version: 1.1.0, last published: an hour ago. Start using react-native-signup-checkbox in your project by running `npm i react-native-signup-checkbox`. There are no other projects in the npm registry using

www.npmjs.com

 

이번에 앱을 만들면서, 시간이 좀 남아서 npm package 를 한번 만들어보았다.

 

 

728x90
반응형
728x90
반응형

ImageBackground 에 이미지를 받고 apk 를 생성하니, 해당 에러가 튀어나왔다.

 

stackoverflow 검색결과 확장자를 바꾸면 해결된다는 이야기가 있었다.

 

디자인 팀에서 받은 png 파일을 jpg 로 바꾼뒤, duplicated 에러를 확인하여

 

./gradlew clean 후 다시 ./gradlew assembleRelease 로 apk 를 생성해줬더니 문제가 해결되었다.

728x90
반응형
728x90
반응형

카메라 녹화를 할때, 해당 기기에서 lux값을 표기해줘야하는 요구사항이 들어왔다.

 

npm 을 검색해봐도 마땅한 패키지가 없어서, https://developer.android.com/guide/topics/sensors/sensors_overview

 

센서 개요  |  Android 개발자  |  Android Developers

대부분의 Android 지원 기기에는 움직임, 방향 및 다양한 환경 조건을 측정하는 센서가 내장되어 있습니다. 이러한 센서는 높은 정밀도와 정확도로 원시 데이터를 제공하며 3차원으로 모니터링하

developer.android.com

를 확인하면서 모듈을 만들기로했다.

 

android/app/src/main/java/패키지이름 최종 폴더트리는

 

이다.

 

AmbientLightSensorModule.java

 


import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import android.widget.Toast;

@ReactModule(name = AmbientLightSensorModule.NAME)
public class AmbientLightSensorModule extends ReactContextBaseJavaModule implements SensorEventListener {

public static final String NAME = "AmbientLightSensor";
private final SensorManager mSensorManager;
private final Sensor mSensorLight;
private final ReactApplicationContext mReactContext;

public AmbientLightSensorModule(ReactApplicationContext reactContext) {
super(reactContext);
mReactContext = reactContext;
mSensorManager = (SensorManager) mReactContext.getSystemService(mReactContext.SENSOR_SERVICE);
mSensorLight = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
}

@Override
@NonNull
public String getName() {
return NAME;
}

private void sendEvent(@NonNull WritableMap params) {
try {
if (mReactContext != null) {
mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("LightSensor", params);
}
} catch (RuntimeException e) {
Log.d("ERROR", "error in sending event");
}
}

@Override
public final void onSensorChanged(SensorEvent sensorEvent) {
WritableMap sensorMap = Arguments.createMap();
float lightSensorValue = sensorEvent.values[0];
sensorMap.putDouble("lightValue", lightSensorValue);
sendEvent(sensorMap);
}

@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
}

@ReactMethod
public void startLightSensor() {
Toast.makeText(getReactApplicationContext(), "startLightSensor 1", Toast.LENGTH_SHORT).show();
if (mSensorLight == null) {
return;
}
Toast.makeText(getReactApplicationContext(), "startLightSensor 2", Toast.LENGTH_SHORT).show();
mSensorManager.registerListener(this, mSensorLight, SensorManager.SENSOR_DELAY_NORMAL);
}

@ReactMethod
public void stopLightSensor() {
Toast.makeText(getReactApplicationContext(), "stopLightSensor 1", Toast.LENGTH_SHORT).show();
if (mSensorLight == null) {
return;
}
Toast.makeText(getReactApplicationContext(), "stopLightSensor 2", Toast.LENGTH_SHORT).show();
mSensorManager.unregisterListener(this);
}

}

LightSensorPackage.java

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class LightSensorPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();

modules.add(new AmbientLightSensorModule(reactContext));

return modules;
}

@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

MainApplication.java 에는 

packages.add(new LightSensorPackage());

를 추가해준다.

js 로 돌아와서, 

 

원하는 컴포넌트에서

 

const [result, setResult] = React.useState<number | undefined>();

useEffect(() => {
NativeModules.AmbientLightSensor.startLightSensor();

const subscription = DeviceEventEmitter.addListener(
'LightSensor',
(data: {lightValue: number}) => {
console.log('data.lightValue :::::::::', data.lightValue);
setResult(data.lightValue);
},
);

return () => {
NativeModules.AmbientLightSensor.stopLightSensor();
subscription.remove();
};
}, []);

을 통해서 console 찍히는것 확인했다.

 

RN을 할수록, Native를 잘하면 더 좋은 앱을 만들수있을것 같다는 생각을 많이 한다.

728x90
반응형
728x90
반응형

회사에서 안드로이드만 사용가능한 기능을 만들어야했다. npm을 찾아보니, 해당기능을 제공하는 라이브러리가 없어서

 

직접 모듈을 구현하기로했다.

 

일단, java 로 해당 모듈을 만들고, 구현하기위해 rn 메인 사이트에 들어가서 예제를 사용해보기로했다.

 

일단 최종 android/app/src/main/java/com/패키지 의 폴더트리는 해당과 같다.

 

CalendarModule.java

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import android.util.Log;

import java.util.HashMap;
import android.widget.Toast;

public class CalendarModule extends ReactContextBaseJavaModule {
CalendarModule(ReactApplicationContext context) {
super(context);
}

@Override
public String getName() {
return "CalendarModule";
}

@ReactMethod
public void createCalendarEvent(String name, String location) {
Log.d("CalendarModule", "Create event called with name: " + name
+ " and location: " + location);
Toast.makeText(getReactApplicationContext(), "HELLO", Toast.LENGTH_SHORT).show();
}

}

MyAppPackage.java

 

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MyAppPackage implements ReactPackage {

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}

@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();

modules.add(new CalendarModule(reactContext));

return modules;
}

}

위 java 파일 최상단에 package 패키지 이름 이 들어가야한다.

 

추후

 

MainApplication.java

해당 package 를 import 해준다.

 

이제 안드로이드 세팅은 끝났고,

 

테스트를 위해 javascript 로 작성하는 부분에서


const {CalendarModule} = NativeModules;

const test = () => {
CalendarModule.createCalendarEvent('testName', 'testLocation');
};

로 작성해주고, test를 실행하면 Logcat에서도 잘보이고, Toast도 잘 뜬다.

 

이제 연결은 되었으니, 필요 모듈을 작성해보면 되겠다!

728x90
반응형
728x90
반응형

android fastlane 은 ios 몇스텝이 추가된다.

 

play console 에서 api 엑세스를 눌러서, 서비스 계정을 등록해야한다. 서비스 계정을 등록하고

 

Play Console 권한 보기에서, 관리자의 권한을 주고나서 시작하면 된다.

 

해당 파일을 android/fastlane/키이름.json 파일에 위치시킨다

나머지는 ios 와 비슷하다.

 

root/android 콘솔에서 fastlane init 을 하면, android/fastlane/키이름.json 위치를 물어보는데, 해당 위치를 fastlane/키이름.json

 

로 입력해주면 된다.

 

fastFile은 위처럼 세팅해놓았다.

 

crashlytics는 firebase 를 사용하기에, 주석처리 해주고

 

deploy시에, gradle 을 clean 하고 aab 파일로 올린다.

 

만났던 에러는

 

Google Api Error: Invalid request - The caller does not have permission

-> 앱권한 설정으로 해결

 

 Google Api Error: Invalid request - APKs are not allowed for this application.

->clean assembleRelease 를 clean bundle 로 변경

 

Google Api Error: Invalid request - Only releases with status draft may be created on draft app.

->

upload_to_play_store(
track: 'internal',
release_status: 'draft',)

로 해결

 

728x90
반응형
728x90
반응형

fastlane 안드로이드를 구축하면서, aab 옵션을 넣어 배포하던중 해당 에러가 생겼다.

 

ios도 fastlane으로 배포하면 종종 같은 에러가나와서, ios에서는 xcode 의 project clean 을 사용했는데

 

같은 원리로

 

안드로이드도

 

./graldew clean 을 통해 gradlew 를 clean 시켜준뒤

 

다시 fastlane을 통해 aab를 만들고, 스토어에 올려보니 해당 문제가 해결되었다.

728x90
반응형
728x90
반응형

지금 회사에서 디자인이 나오기전까지 일주일정도의 개발 여유 시간을 주셨다.

일주일 동안 fastlane 과 테스트코드를 적용한다고 말씀드리니, 흔쾌히 하도록 해주셨다.

안해본것들을 해보고 검증하는 시간을 주는 회사가 개발자 입장에서는 참 좋다.

 

일단 fastlane 을 설치해준다. 나는 homebrew를 선호하기에, 

brew install fastlane

 로 설치를 해준다.

 

현재 프로젝트 root 에서

cd ios
fastlane init

를 하면

 

터미널에서 fastlane을 어떻게 이용할지에 대해서 물어본다.

테스트플라이트에 업로드 후, 배포를 할예정이기에, 2번을 선택해주면

 

다음엔 스키마 선택이 나온다.

 

환경에 따라 테스트를 위해 스키마를 dev / pro 로 나누어 놓았는데, 테스트 플라이트에 올려서 테스트할 것은 pro 환경이기에

해당 스키마를 선택해준다.

 

애플 개발자계정과 비밀번호를 입력하고 나면

 

이런 파일 구조가 생긴다. (.env.default 는 제외)

 

fastlane 밑에 .env.default 를 만들고 애플 개발자계정에서 앱키를 발급받은 값을 넣어준다

FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD=앱키

Fastfile파일로 이동하면

 

# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#
# For a list of all available plugins, check out
#
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
increment_build_number(xcodeproj: "프로젝트.xcodeproj")
build_app(workspace: "프로젝트.xcworkspace", scheme: "선택한스키마")
upload_to_testflight
end
end

파일이 있다.

 

해당 파일에서 자동으로 버전을 업데이트 해주고싶다면

 

default_platform(:ios)

platform :ios do

  def updateVersion(options)
    if options[:version]
      version = options[:version]
    else
      version = prompt(text: "버전을 입력해주세요 (ex 1.0.0) : ")
    end

    re = /\d+.\d+.\d+/
    versionNum = version[re, 0]

    if (versionNum)
      increment_version_number(
        version_number: versionNum
      )
    elsif (version == 'major' || version == 'minor' || version == 'patch')
      increment_version_number(
        bump_type: version
      )
    elsif (version == 'maintain')
    else
      UI.user_error!("[ERROR] Wrong version!!!!!!")
    end
  end


  desc "Push a new beta build to TestFlight"
  lane :beta do |options|
    cert
    sigh(force: true)
    updateVersion(options)

    increment_build_number(xcodeproj: "myApp.xcodeproj")
    build_app(workspace: "myApp.xcworkspace", scheme: "myAppScheme")
    upload_to_testflight
  end
end

해당 updateVersion 이라는 함수를 사용해주면되는데

 

나는 안드로이드 / ios 버전을 맞추는것을 좋아하기에, 빌드 버전은 수동으로 맞출 예정이라 생략했다.

 

마지막으로 package.json에

 

 

fastlane 배포 명령어를 넣어주면 끝.

 

yarn fastlane:ios 를 통해 현재 버전을 testflight 로 업데이트할수있다.

 

제일 좋은것은 archive, upload, testflight의 수출관리규정을 수동으로 하지 않아도된다는점

728x90
반응형
728x90
반응형

react-native-video 의 poster 를 보면

type 이 string 이다.

 

이는 내부적으로 source={{uri : string }}

 

형태로 작동된다.

 

patch-package 를 사용할까 하다가 안드로이드에서 리소스들을 string 값으로 가져오던것이 생각나서 RN 에도 있을까해서 검색해보았다

 

Image.resolveAssetSource

가 해당 됐다.

Image.resolveAssetSource(require('./path/to/assets/poster.png')).uri

로 해당 image 의 uri 값을 string 으로 얻을수 있고 해당 uri string으로 넣을수있다.

728x90
반응형
728x90
반응형

좌우, 상하의 스크롤시에 해당 스와이프 유무를 체크하기 위한 훅

 

import { Dimensions } from 'react-native';
const windowWidth = Dimensions.get('window').width;

export function useSwipe(
onSwipeLeft?: any,
onSwipeRight?: any,
rangeOffset = 4,
) {
let firstTouch = 0;

function onTouchStart(e: any) {
firstTouch = e.nativeEvent.pageX;
}

function onTouchEnd(e: any) {
const positionX = e.nativeEvent.pageX;
const range = windowWidth / rangeOffset;

if (positionX - firstTouch > range) {
onSwipeRight && onSwipeRight();
} else if (firstTouch - positionX > range) {
onSwipeLeft && onSwipeLeft();
}
}

return { onTouchStart, onTouchEnd };
}

사용은

 

export const IntroModal = ({ isVisible, onClose }: IntroModalProps) => {
const { onTouchStart, onTouchEnd } = useSwipe(onSwipeLeft, onSwipeRight, 15);

function onSwipeLeft() {
console.log('IntroModal SWIPE_LEFT');
onClose();
}

function onSwipeRight() {
console.log('IntroModal SWIPE_RIGHT');
}

return (
<Modal
isVisible={isVisible}
animationIn={'slideInLeft'}
animationOut={'slideOutLeft'}
style={styles.modal}
>
<ScrollView
onTouchStart={onTouchStart}
onTouchCancel={onTouchEnd}
onTouchEnd={onTouchEnd}
style={{ flex: 1, backgroundColor: 'blue' }}
pagingEnabled
>
<View
style={{
backgroundColor: 'blue',
flex: 1,
width: deviceWidth,
height: deviceHeight,
}}
></View>
<View
style={{
backgroundColor: 'red',
flex: 1,
width: deviceWidth,
height: deviceHeight,
}}
></View>
<View
style={{
backgroundColor: 'yellow',
flex: 1,
width: deviceWidth,
height: deviceHeight,
}}
></View>
</ScrollView>
</Modal>
);
};

이런식으로하면 된다.

 

ScrollView pagingEabled 에서 상하를 판독하고,

 

hook에서 좌우를 판독함

 

onTouchEnd 에서 가끔 failed 가 뜨기에

 

onTouchCancel에 도 넣어주니 정상 작동한다.

728x90
반응형

+ Recent posts