2022. 1. 23. 23:55ㆍReact Native/Basic

1. Image Picker 설치
react-native init RN_ImagePicker
위의 명령어로 새로운 프로젝트를 하나 만든다. 프로젝트 생성이 완료되면 해당 프로젝트로 이동하고 아래 명령어를 통해 Image Picker library를 설치해준다.
npm install --save react-native-image-picker
설치가 완료되면 ios 폴더로 가서 pod install 을 실행한다.
cd ios
pod install
cd ..
파일로 가서 Add folder to workspace를 통해 해당 폴더를 추가한다.
안드로이드는 추가로 해줄 일이 있다. 카메라랑 라이브러리에 접근 가능하도록 접근권한을 줘야 한다.
/android/app/src/main/AdndroidManifest.xml 파일로 가서 아래 코드를 추가해주자.
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
그리고 App.js 파일로 가서 우리 목적에 맞게 수정을 해보자.
실행했더니 ios 시뮬레이터 구동이 잘 된 걸로 보아 문제는 없다. 혹시 여기서 에러가 나면 ios 폴더로 가서 pod install 을 다시 해보는게 어떨까
App.js
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {Component} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Image,
Button
} from 'react-native';
import ImagePicker from 'react-native-image-picker'
class App extends Component {
render() {
return (
<View style={styles.container}>
<Text>Hello</Text>
</View>
)
}
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#c4ab26'
}
});
export default App;
2. 카메라 & 이미지 갤러리 사용하기
이미지를 불러오기 위한 추가코딩을 할 건데 불러올 사진을 담을 변수를 state에 선언하자. avatar라는 변수를 만들고 <View>에는 <Image />를 추가하는데 Image의 source라는 프로퍼티에 uri:this.state.avatar를 할당하고 style을 주자. 그리고 <Image/> 아래에 <Button/>을 추가하자. onPress되면 this.addImage()라는 함수를 불러오도록 하는데 addImage는 ImagePicker라는 라이브러리에 있는 launchCamera() 메소드를 불러온다. 그리고 launchCamera는 콜백함수를 갖는데 사진을 기동시켜서 사진을 찍을 것이기 때문에 그 response를 받아와서 state.avatar 값을 갱신시켜준다. 그럼 아래 코드와 같은 함수를 만들게 된다.
addImage = () => {
ImagePicker.launchCamera({}, response=>{
this.setState({
avatar: response.uri
})
})
}
ios를 구동시켜서 실행하는데 버튼을 눌러도 아무런 동작을 하지 않는다. 이건 ios 시뮬레이터가 카메라를 구동시키는 기능을 지원하지 않아서 그런 것으로 안 되는 게 정상이다.
App.js
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {Component} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Image,
Button,
} from 'react-native';
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
class App extends Component {
state = {
avatar: '',
};
addImage = () => {
launchCamera({}, response => {
this.setState({
avatar: response.uri,
});
});
};
render() {
return (
<View style={styles.container}>
<Image source={{uri: this.state.avatar}} style={styles.avatar} />
<Button
title="Add an Image"
onPress={() => {
this.addImage();
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#c4ab26',
},
avatar: {
width: '100%',
height: 400,
},
});
export default App;
ios에서 구동시키면 아무런 동작을 하지 않는데 이는 당연한 것이다.
안드로이드를 구동시키면 아무런 동작을 하지 않는데 그 이유는 다음 링크를 참고하자. 간단히 설명하자면 권한 설정이 자동으로 되어 권한 설정을 해줄 필요가 없으며 ImagePicker를 import하고 사용할 때 방식이 약간 변경되었기 때문이다.
https://www.inflearn.com/questions/115453
ImagePicker Android 에뮬레이터 오류 - 인프런 | 질문 & 답변
안녕하세요. 강의 잘 듣고 있습니다. 정확하지는 않지만, ImagePicker 라이브러리의 버전에 따라 생기는 오류인 것 같습니다. ------------------------------------------------------------------------------...
www.inflearn.com
2022.01.05 - [React Native/Basic] - [React Native] error/solutionerror/solution
[React Native] error/solution
1. react-native init [project_name] RuntimeError: abort(Error: Failed to install CocoaPods dependencies for iOS project, which is required by this template. Please try again manually: "cd .//Users/..
byein.tistory.com
위의 해결방법에 따라 해결하면 권한을 묻는 과정이 없이 바로 실행된다. 카메라를 동작시키면 option키는 누르고 wasd로 움직이거나 마우스로 회전 가능하다.
그런데 위의 과정에 따라 진행하면 이미지가 출력되지 않는다.
https://www.inflearn.com/questions/375119
이미지 업데이트가 안 되는 이슈 해결 / showImagePicker 대체 방법 질문드립니다. - 인프런 | 질문 &
안녕하세요. 안드로이드로 실습하는 중에 이미지 업데이트가 안 되어서 문의드리려다가 해결해서 공유하며, 질문도 하나 드립니다. 질문은 맨 아래에 있습니다. 현재 react-native는 0.66.4 버전
www.inflearn.com
위의 링크에서 이미지 업데이트 부분에 대한 해결방법을 설명한다. 아래 코드처럼 수정하여 해결 가능하다. response 안에 assets로 들어가고 그 안에 있는 uri의 주소를 받아오기 위해서는 response.uri 를 response.assets[0].uri로 바꿔야 한다.
App.js
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {Component} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Image,
Button,
} from 'react-native';
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
class App extends Component {
state = {
avatar: '',
};
addImage = () => {
launchCamera({}, response => {
// console.warn(response.assets[0].uri);
this.setState({
avatar: response.assets[0].uri
})
})
}
render() {
return (
<View style={styles.container}>
<Image source={{uri: this.state.avatar}} style={styles.avatar} />
<Button
title="Add an Image"
onPress={() => {
this.addImage();
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#e4ab26',
},
avatar: {
width: '100%',
height: 400,
},
});
export default App;
위에서는 바로 카메라로 이동했지만 이젠 약간 변경하여 찍어둔 사진을 가져와보도록 하자. 방법은 간단하다. launchCamera를 lauchImageLibrary로 바꿔주기만 하면 된다. 이 코드는 ios와 안드로이드 모두 다 잘 동작한다.
그리고 위의 2가지를 한 번에 지원하는 메소드가 있다. 새로운 사진을 찍을지 이미 있는 사진을 불러올지 선택이 가능한 메소드다. 아까 바꾼 launchImageLibary를 showImagePicker로 바꿔주면 된다.
addImage = () => {
showImagePicker({
title: 'Choose your photo',
takePhtoButtonTitle: 'Take a pretty one',
chooseFromLibraryButtonTitle: 'Select an old one',
cancelButtonTitle: 'Just go back'
}, response => {
// console.warn(response.assets[0].uri);
this.setState({
avatar: response.assets[0].uri
})
})
}
하지만 위의 코드를 작성하면 에러가 난다.
https://www.inflearn.com/questions/271445
showImagePicker 오류 - 인프런 | 질문 & 답변
안녕하세요! 다른 댓글을 참고해 launchCamer와 launchLibrary 오류는 해결하였는데 [사진] showImagePicker 오류는 해결되지 않아 질문드립니다.. import { launchCamera, launchImageLibrary, showImageP...
www.inflearn.com
위의 링크에서 보듯이 이후로 showImagePicker는 더 이상 제공되지 않고 있다. 따라서, launchCamera 또는 launchImageLibrary 중 하나를 선택할 수밖에 없다. 유저에게 두 개의 option을 모두 제공하고 싶다면, 두 개의 버튼을 만들고 각각의 버튼의 이벤트 핸들러로 처리하는 방법이 있을 수 있다고 한다.
3. React Native Contacts 설치
연락처에 접근하는 방법
https://github.com/morenoh149/react-native-contacts
GitHub - morenoh149/react-native-contacts: React Native Contacts
React Native Contacts. Contribute to morenoh149/react-native-contacts development by creating an account on GitHub.
github.com
위의 깃헙 페이지로 이동해서 아래 코드를 입력해 설치한다.
npm install react-native-contacts --save
그리고 react native 버전이 0.6x 이상인 경우에는 아래 코드를 ios/Podfile에 추가하도록 하자.
target 'app' do
...
pod 'react-native-contacts', :path => '../node_modules/react-native-contacts' <-- add me
...
end
Podfile
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
platform :ios, '11.0'
target 'RN_ImagePicker' do
config = use_native_modules!
use_react_native!(
:path => config[:reactNativePath],
# to enable hermes on iOS, change `false` to `true` and then install pods
:hermes_enabled => false
)
pod 'react-native-contacts', :path => '../node_modules/react-native-contacts'
target 'RN_ImagePickerTests' do
inherit! :complete
# Pods for testing
end
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
use_flipper!()
post_install do |installer|
react_native_post_install(installer)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
end
end
그리고 이번엔 xcode를 열어서 현재 프로젝트를 열어보자. 현재 프로젝트의 ios 파일을 open 하면 된다. 그리고 프로젝트 폴더를 연 뒤에 info.plist 라는 파일을 열자. Information Property List에서 추가버튼을 눌러서 Privacy - Contacts Usage Description 라는 것을 추가해준다. value 값은 빈 칸으로 둬도 괜찮다. 연락처에 접근하기 위한 권한만 주는 것이기 때문이다.
그리고 안드로이드도 추가작업이 필요하다. 깃헙페이지에 가 보면 Android부분을 보면 android/settings.gradle에 아래 코드를 추가한다.
...
include ':react-native-contacts'
project(':react-native-contacts').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-contacts/android')
rootProject.name = 'RN_ImagePicker'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
include ':react-native-contacts'
project(':react-native-contacts').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-contacts/android')
...
dependencies {
...
implementation project(':react-native-contacts')
}
apply plugin: "com.android.application"
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation. If none specified and
* // "index.android.js" exists, it will be used. Otherwise "index.js" is
* // default. Can be overridden with ENTRY_FILE environment variable.
* entryFile: "index.android.js",
*
* // https://reactnative.dev/docs/performance#enable-the-ram-format
* bundleCommand: "ram-bundle",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // whether to disable dev mode in custom build variants (by default only disabled in release)
* // for example: to disable dev mode in the staging build type (if configured)
* devDisabledInStaging: true,
* // The configuration property can be in the following formats
* // 'devDisabledIn${productFlavor}${buildType}'
* // 'devDisabledIn${buildType}'
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"],
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
project.ext.react = [
enableHermes: false, // clean and rebuild if changing
]
apply from: "../../node_modules/react-native/react.gradle"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore.
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
/**
* Whether to enable the Hermes VM.
*
* This should be set on project.ext.react and that value will be read here. If it is not set
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
* and the benefits of using Hermes will therefore be sharply reduced.
*/
def enableHermes = project.ext.react.get("enableHermes", false);
/**
* Architectures to build native code for in debug.
*/
def nativeArchitectures = project.getProperties().get("reactNativeDebugArchitectures")
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.rn_imagepicker"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
if (nativeArchitectures) {
ndk {
abiFilters nativeArchitectures.split(',')
}
}
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
// Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
defaultConfig.versionCode * 1000 + versionCodes.get(abi)
}
}
}
}
dependencies {
implementation project(':react-native-contacts')
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.implementation
into 'libs'
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
// MainApplication.java
import com.rt2zz.reactnativecontacts.ReactNativeContacts; // <--- import
public class MainApplication extends Application implements ReactApplication {
......
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ReactNativeContacts()); // <------ add this
}
......
}
MainApplication.java
package com.rn_imagepicker;
import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import com.rt2zz.reactnativecontacts.ReactNativeContacts;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("com.rn_imagepicker.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
그리고 Permission을 보면 app/src/main/AndroidManifest.xml 파일로 가서 write 뿐만이 아니라 read도 해야한다.
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rn_imagepicker">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!--
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> -->
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
시뮬레이터를 구동시켰을 때 아무 문제없이 잘 실행되면 사용할 준비가 됐다.
4. 연락처에 접근하기
render 함수에 버튼만 남기고 다 지워준다. onPress 시에 getContacts를 호출하게 한다. getContacts는 연락처에 접근할 수 있는 권한이 있느냐 없느냐를 판단하는 함수를 호출할 건데 이건 requestContactPermission이라는 이름으로 직접 만들 거다. 여기서 true, false로 리턴할 것이기 때문에 그 값을 didGetPermission으로 받아온다. 만약에 didGetPermission 값이 true이면 연락처에 있는 모든 정보를 불러온다. 그런데 만약 에러가 생기면 에러를 표시하고 그렇지 않으면 console.warn을 통해 연락처를 표시한다. 그런데 didGetPermission이 false였으면 권한이 없는 것으로 'no permission'을 alert 창을 띄우도록 한다.
requestContactPermission 함수를 만들 것인데 그 전에 react-native에서 import 할 항목들이 있다. 우선 플랫폼별로 분기시켜 권한 여부를 판단할 것이므로 Plamform을 import 해줘야 한다. 그리고 안드로이드의 권한 정책이 강화되면서 안드로이드 권한 여부를 판단할 때 쓰일 PermissionsAndroid를 import 한다.
requestContactPermission 함수는 async-await 함수로 선언할 것인데 async-await 함수는 자바스크립트의 비동기 처리를 위한 문법이다.
우선 처음에 Platform을 이용해서 OS분기를 시켜준다. ios면 console.warn에 ios임을 띄워주고 그게 아니면 안드로이드에서는 Android를 console.warn에 띄워준다. 이제 그 다음부터 중요한데 granted 라는 변수에 await PermissionsAndroid.requestMultiple를 넣어줄 건데 PermissionsAndroid.requestMultiple 안에 배열로 [PermissionsAndroid.PERMISSINON.WRITE_CONTACTS, PermissionsAndroid.PERMISSION.READ_CONTACTS]를 넣어준다. await 을 통해 권한 여부를 확인받는 것이다. 비동기 처리라고 하면 await 뒤의 내용들이 다 처리될 때까지 기다리고 그 값을 받아오는 것이다. 권한이 주어졌는지 아닌지를 판단하는 것은 매우 중요하며 그 여부를 알아야 다음 내용을 진행할 수 있기 때문에 await을 쓰는 것이고 연락처에 접근하고 수정할 권한이 있는지 판단하는 것이다.
그리고 granted에 있는 연락처에 접근하는 권한이 안드로이드가 가진 권한과 같고 granted에 있는 연락처를 수정할 권한이 안드로이드가 가진 권한과 같다면 true를 리턴하고 아니면 false를 리턴한다.
안드로이드는 처음에 아무런 연락처가 저장되어 있지 않아 빈 배열이 뜰텐데 연락처를 추가하면 결과를 확인 가능하다.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {Component} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Image,
Button,
Platform,
PermissionsAndroid,
} from 'react-native';
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
import Contacts from 'react-native-contacts';
class App extends Component {
async requestContactPermission() {
if (Platform.OS == 'ios') {
console.warn('iOS');
return true;
} else {
console.warn('Android');
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_CONTACTS,
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
]);
if (
granted['android.permission.READ_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED &&
granted['android.permission.WRITE_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED
) {
return true;
} else {
return false;
}
}
}
getContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
Contacts.getAll((err, contacts) => {
if (err) {
throw err;
}
console.warn(contacts);
});
} else {
alert('no permission');
}
});
};
render() {
return (
<View style={styles.container}>
<Button
title="Load Contacts"
onPress={() => {
this.getContacts();
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#e4ab26',
},
avatar: {
width: '100%',
height: 400,
},
});
export default App;
위의 내용을 정리하면 위와 같은 코드가 나오지만 버전이 업데이트 되면서 에러를 마주하게 된다.
https://www.inflearn.com/questions/138495
react-native-contacts 6버전에서 바꿔줘야합니다. - 인프런 | 질문 & 답변
최신버전에서는 아래와 같이 바꿔줘야합니다. //contacts 6버전대 Contacts.getAll() .then((contacts) =>&...
www.inflearn.com
위의 링크를 참고하여 수정하면 다음과 같은 코드가 된다.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {Component} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Image,
Button,
Platform,
PermissionsAndroid,
} from 'react-native';
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
import Contacts from 'react-native-contacts';
class App extends Component {
async requestContactPermission() {
if (Platform.OS == 'ios') {
console.warn('iOS');
return true;
} else {
console.warn('Android');
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_CONTACTS,
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
]);
if (
granted['android.permission.READ_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED &&
granted['android.permission.WRITE_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED
) {
return true;
} else {
return false;
}
}
}
getContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
Contacts.getAll().then((contacts)=>{
console.warn(contacts);
// setMyContacts(contacts);
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
};
render() {
return (
<View style={styles.container}>
<Button
title="Load Contacts"
onPress={() => {
this.getContacts();
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#e4ab26',
},
avatar: {
width: '100%',
height: 400,
},
});
export default App;
5. 연락처 추가하기
앱 상에서 연락처를 추가하는 방법을 알아보자.
우선 확인하기 편리하도록 콘솔 창이 아닌 화면에 연락처를 띄우도록 하자. myContacts라는 state 값을 추가하고 console.warn 대신 this.setState를 통해 contacts 값을 할당한다. 그리고 View 안에 map을 통해 모든 item의 항목들을 가져오도록 한다. 그럼 연락처가 잘 가져와 진다.
이젠 추가해본다. 버튼을 새로 하나 만들어 onPress 시에 addContacts 라는 함수를 호출한다. 이 함수를 새로 만들 함수로 getContact와 유사하지만 수정을 좀 해줘야 한다. newContact 라는 변수를 만들고 정보를 넣어준다. 그리고 addContact를 통해 방금 기입한 정보를 가진 변수를 넘겨주고 this.setState 대신 this.getContacts를 넣어준다. getContacts를 호출해서 우리가 추가한 연락처가 state에 선언한 myContacts에 넣어주는 것이다.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {Component} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Image,
Button,
Platform,
PermissionsAndroid,
} from 'react-native';
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
import Contacts from 'react-native-contacts';
class App extends Component {
state = {
myContacts: [],
}
async requestContactPermission() {
if (Platform.OS == 'ios') {
console.warn('iOS');
return true;
} else {
console.warn('Android');
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_CONTACTS,
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
]);
if (
granted['android.permission.READ_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED &&
granted['android.permission.WRITE_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED
) {
return true;
} else {
return false;
}
}
}
getContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
Contacts.getAll().then((contacts)=>{
this.setState({
myContacts: contacts
})
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
};
addContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
const newContact = {
emailAddress: [{
label: "work",
email: "aaa@example.com",
}],
familyName : "Go",
givenName : "Gildong",
phoneNumbers: [{
label: "mobile",
number: "(010) 1111-1111"
}]
}
Contacts.addContact().then((newContact)=>{
this.getContacts();
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
}
render() {
return (
<View style={styles.container}>
{
this.state.myContacts.map((item, idx) => (
<Text key={idx}>
{item.givenName} {item.familyName}
</Text>
))
}
<Button
title="Load Contacts"
onPress={() => {
this.getContacts();
}}
/>
<Button
title="Add Contacts"
onPress={() => this.addContacts()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#e4ab26',
},
avatar: {
width: '100%',
height: 400,
},
});
export default App;
위 코드는 에러가 발생하는 이전 버전의 코드이다. 위 코드를 아래 코드와 같이 수정하여야 한다.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {Component} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Image,
Button,
Platform,
PermissionsAndroid,
} from 'react-native';
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
import Contacts from 'react-native-contacts';
class App extends Component {
state = {
myContacts: [],
}
async requestContactPermission() {
if (Platform.OS == 'ios') {
console.warn('iOS');
return true;
} else {
console.warn('Android');
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_CONTACTS,
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
]);
if (
granted['android.permission.READ_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED &&
granted['android.permission.WRITE_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED
) {
return true;
} else {
return false;
}
}
}
getContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
Contacts.getAll().then((contacts)=>{
this.setState({
myContacts: contacts
})
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
};
addContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
const newContact = {
emailAddress: [{
label: "work",
email: "aaa@example.com",
}],
familyName : "Go",
givenName : "Gildong",
phoneNumbers: [{
label: "mobile",
number: "(010) 1111-1111"
}]
}
Contacts.addContact(newContact).then(()=>{
this.getContacts();
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
}
render() {
return (
<View style={styles.container}>
{
this.state.myContacts.map((item, idx) => (
<Text key={idx}>
{item.givenName} {item.familyName}
</Text>
))
}
<Button
title="Load Contacts"
onPress={() => {
this.getContacts();
}}
/>
<Button
title="Add Contacts"
onPress={() => this.addContacts()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#e4ab26',
},
avatar: {
width: '100%',
height: 400,
},
});
export default App;
위의 코드를 실행하면 ios, 안드로이드 모두 잘 실행된다.
여기서는 정해진 값을 추가해봤는데 아래에서는 입력값을 추가하는 것을 배워보자.
+) 참고로 addContacts에 정보를 추가한 것은 깃헙 페이지에서 Example Contact Record를 참고하면 된다.
https://github.com/morenoh149/react-native-contacts#example-contact-record
GitHub - morenoh149/react-native-contacts: React Native Contacts
React Native Contacts. Contribute to morenoh149/react-native-contacts development by creating an account on GitHub.
github.com
6. 연락처 Form 활용
Form을 여는 버튼을 새로 만들자. onPress 시 openForm이라는 함수를 불러오고 openForm은 Contacts의 내장함수인 openContactFrom을 불러오자. 에러가 발생 시 console에 띄워주도록 한다. 강의를 따라 아래 코드를 작성하고 실행하면 에러가 나는데 버전이 바뀌며 업데이트 됐기 때문이다.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {Component} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Image,
Button,
Platform,
PermissionsAndroid,
} from 'react-native';
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
import Contacts from 'react-native-contacts';
class App extends Component {
state = {
myContacts: [],
}
async requestContactPermission() {
if (Platform.OS == 'ios') {
console.warn('iOS');
return true;
} else {
console.warn('Android');
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_CONTACTS,
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
]);
if (
granted['android.permission.READ_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED &&
granted['android.permission.WRITE_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED
) {
return true;
} else {
return false;
}
}
}
getContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
Contacts.getAll().then((contacts)=>{
this.setState({
myContacts: contacts
})
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
};
addContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
const newContact = {
emailAddress: [{
label: "work",
email: "aaa@example.com",
}],
familyName : "Go",
givenName : "Gildong",
phoneNumbers: [{
label: "mobile",
number: "(010) 1111-1111"
}]
}
Contacts.addContact(newContact).then(()=>{
this.getContacts();
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
}
openForm = () => {
Contacts.openContactForm({},(err) => {
if(err) console.warn(err)
})
}
render() {
return (
<View style={styles.container}>
{
this.state.myContacts.map((item, idx) => (
<Text key={idx}>
{item.givenName} {item.familyName}
</Text>
))
}
<Button
title="Load Contacts"
onPress={() => {
this.getContacts();
}}
/>
<Button
title="Add Contacts"
onPress={() => this.addContacts()}
/>
<Button
title="Open Form"
onPress={() => this.openForm()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#e4ab26',
},
avatar: {
width: '100%',
height: 400,
},
});
export default App;
위의 코드를 아래 코드로 바꾸면 에러 없이 잘 동작한다. 여기서 값을 입력한 뒤에 연락처를 불러오면 잘 읽어들인다.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {Component} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Image,
Button,
Platform,
PermissionsAndroid,
} from 'react-native';
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
import Contacts from 'react-native-contacts';
class App extends Component {
state = {
myContacts: [],
}
async requestContactPermission() {
if (Platform.OS == 'ios') {
console.warn('iOS');
return true;
} else {
console.warn('Android');
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_CONTACTS,
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
]);
if (
granted['android.permission.READ_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED &&
granted['android.permission.WRITE_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED
) {
return true;
} else {
return false;
}
}
}
getContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
Contacts.getAll().then((contacts)=>{
this.setState({
myContacts: contacts
})
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
};
addContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
const newContact = {
emailAddress: [{
label: "work",
email: "aaa@example.com",
}],
familyName : "Go",
givenName : "Gildong",
phoneNumbers: [{
label: "mobile",
number: "(010) 1111-1111"
}]
}
Contacts.addContact(newContact).then(()=>{
this.getContacts();
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
}
openForm = () => {
Contacts.openContactForm({}).then(() => {
}).catch((err)=>{
console.error(err);
throw err;
})
}
render() {
return (
<View style={styles.container}>
{
this.state.myContacts.map((item, idx) => (
<Text key={idx}>
{item.givenName} {item.familyName}
</Text>
))
}
<Button
title="Load Contacts"
onPress={() => {
this.getContacts();
}}
/>
<Button
title="Add Contacts"
onPress={() => this.addContacts()}
/>
<Button
title="Open Form"
onPress={() => this.openForm()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#e4ab26',
},
avatar: {
width: '100%',
height: 400,
},
});
export default App;
여기에 추가로 ios에서만 가능한 기능이 있다. 위에서 만든 newContact의 값을 openForm 안에 정의하고 openContactForm에 newContact를 넘겨주면 Form을 실행할 때 자동으로 그 값이 들어가 있는 상태로 로드된다.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {Component} from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
Image,
Button,
Platform,
PermissionsAndroid,
} from 'react-native';
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
import Contacts from 'react-native-contacts';
class App extends Component {
state = {
myContacts: [],
}
async requestContactPermission() {
if (Platform.OS == 'ios') {
console.warn('iOS');
return true;
} else {
console.warn('Android');
const granted = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.WRITE_CONTACTS,
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
]);
if (
granted['android.permission.READ_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED &&
granted['android.permission.WRITE_CONTACTS'] ===
PermissionsAndroid.RESULTS.GRANTED
) {
return true;
} else {
return false;
}
}
}
getContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
Contacts.getAll().then((contacts)=>{
this.setState({
myContacts: contacts
})
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
};
addContacts = () => {
this.requestContactPermission().then(didGetPermission => {
if (didGetPermission) {
const newContact = {
emailAddress: [{
label: "work",
email: "aaa@example.com",
}],
familyName : "Go",
givenName : "Gildong",
phoneNumbers: [{
label: "mobile",
number: "(010) 1111-1111"
}]
}
Contacts.addContact(newContact).then(()=>{
this.getContacts();
}).catch((err)=>{
console.error(err);
throw err;
});
} else {
alert('no permission');
}
});
}
openForm = () => {
const newContact = {
emailAddress: [{
label: "work",
email: "aaa@example.com",
}],
familyName : "ccccc",
givenName : "ddddd",
phoneNumbers: [{
label: "mobile",
number: "(010) 1111-1111"
}]
}
Contacts.openContactForm(newContact).then(() => {
}).catch((err)=>{
console.error(err);
throw err;
})
}
render() {
return (
<View style={styles.container}>
{
this.state.myContacts.map((item, idx) => (
<Text key={idx}>
{item.givenName} {item.familyName}
</Text>
))
}
<Button
title="Load Contacts"
onPress={() => {
this.getContacts();
}}
/>
<Button
title="Add Contacts"
onPress={() => this.addContacts()}
/>
<Button
title="Open Form"
onPress={() => this.openForm()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#e4ab26',
},
avatar: {
width: '100%',
height: 400,
},
});
export default App;
참고 자료
iOS/Android 앱 개발을 위한 실전 React Native - Basic - 인프런 | 강의
Mobile App Front-End 개발을 위한 React Native의 기초 지식 습득을 목표로 하고 있습니다. 진입장벽이 낮은 언어/API의 활용을 통해 비전문가도 쉽게 Native Mobile App을 개발할 수 있도록 제작된 강의입니다
www.inflearn.com
'React Native > Basic' 카테고리의 다른 글
[React Native] Good To Know Things (0) | 2022.01.24 |
---|---|
[React Native] Animation (0) | 2022.01.24 |
[React Native] React Navigation - 16. Nesting Navigators(화면 구조 설계) (0) | 2022.01.23 |
[React Native] React Navigation - 15. React Native Vector Icons 활용 (0) | 2022.01.20 |
[React Native] React Navigation - 14. React Native Vector Icons 설치 (0) | 2022.01.20 |