Loading..

BTT 카카오톡 프리셋 개발 후기

2019.10.22 11:43

BTT 카카오톡 프리셋 개발 후기

macOS 유저들이 많이 사용하는 BetterTouchTool이라는 트윅이 있습니다. 저는 터치바 관련 트윅을 위해 BTT와 GoldenChaos-BTT 프리셋을 이용하고 있는데, 페이스북 메신저 등의 알림수를 표시해주는 아이콘이 마음에 들어서 카카오톡도 이렇게 볼 수 있으면 좋겠다 싶어 해당 아이콘을 개발했습니다.

갑자기 확 꽂혀서 만들었는데, 기존 아이콘들을 참고하니 생각보다 만들기 간단했습니다. 만든 김에 해당 프리셋을 공유하려고 글을 작성했습니다.
https://gist.github.com/kentakang/7852bf52201ff5c40e53828b7008f9ac
프리셋은 위 링크에서 다운로드 하실 수 있습니다.

IntersectionObserver로 구현하는 React 무한 스크롤

2019.08.17 17:22

IntersectionObserver로 구현하는 React 무한 스크롤

오늘은 React에서 IntersectionObserver를 통해 무한 스크롤을 구현해보려 합니다. 요즘 SPA에서는 보통 페이징을 무한 스크롤을 통해 구현하는 경우가 많은데, 무한 스크롤을 구현하는 방법으로는 여러가지가 있습니다. 이번에는 자바스크립트의 Intersection Observer API를 사용해서 구현해보겠습니다.

Intersection Observer가 뭔데요?

IntersectionObserver API는 element가 viewport, 다른 엘리먼트와의 관계에서 보이는지 안보이는지를 알 수 있도록 하는 API입니다. 많이 사용되는 경우는 지금 제가 구현하려고 하는 무한 스크롤이나, 이미지의 lazyload 등이 있겠습니다. 현재 IE를 제외한 대부분 브라우저의 최신 버전에서는 지원하고 있고, 디테일한 버전은 caniuse 에서 확인 하실 수 있습니다.

Intersection Observer를 어떻게 활용하나요?

IntersectionObserver 객체의 경우 callback, options를 매개변수로 받습니다. options 객체의 경우 root, rootMargin, threshold 값을 받습니다.

callback

callback 함수는 관측 대상이 threshold 만큼 보일 때, 호출되는 함수입니다. 관측 대상으로 지정한 DOM Element 객체가 entires, observer 자기 자신인 observer가 매개 변수로 전달됩니다.

root

root의 값은 element, null 중 하나입니다. 관측 대상을 감싸는 element가 들어가거나, null로 지정할 경우 viewport가 들어갑니다. 문서 내에 따로 스크롤이 가능한 요소가 있고, 관측 대상이 해당 요소 안에 있다면 root에는 스크롤 가능한 요소가 들어가게 됩니다.

rootMargin

rootMargin의 경우 위에서 지정한 root 요소를 감싸는 margin 값이 들어갑니다. css margin과 같이 px, % 등의 단위로 작성할 수 있습니다.

threshold

관측 대상이 root와 몇 % 교차했을때, 지정해준 callback 함수를 실행할지 결정하는 값입니다. threshold의 값은 float 값이나, float의 배열이 들어갈 수 있습니다. 만약 마지막 요소가 10% 정도 보일때, 다음 페이지 요소를 로딩하는 callback을 실행한다면, threshold의 값은 0.1이 됩니다.

IntersectionObserver의 구성 요소를 알아봤으니, 실제로 구현해보도록 하겠습니다.

무한 스크롤 구현

우선 제가 구현하고 있는 페이지는 아래 코드와 같이 작성되어 있습니다.

import React, { useState } from "react";
import { useQuery } from "react-apollo-hooks";
import {
  Page,
  Header,
  Content,
  Search,
  GridList,
  Card
} from "../components/Page";
import { getThumbURL } from "../utils";
import GET_VIDEOS from "../Queries/Video";

const Main = () => {
  const [page, setPage] = useState(1);

  const { data } = useQuery(GET_VIDEOS, {
    variables: {
      page
    }
  });

  return (
    <Page>
      <Header>
        <Search placeholder=" 검색어를 입력해주세요" />
      </Header>
      <Content>
        <GridList>
          {data !== undefined &&
            data.videos.map(video => (
              <Card
                key={video._id}
                thumb={getThumbURL(video.filePath)}
                name={video.name}
              />
            ))}
        </GridList>
      </Content>
    </Page>
  );
};

export default Main;

마지막 엘리먼트의 ref를 추가하고, 해당 ref를 통해 IntersectionObserver를 통한 무한 스크롤을 구현해보도록 하겠습니다.

import React, { useState, useRef, useEffect } from "react";
import { useQuery } from "react-apollo-hooks";
import {
  Page,
  Header,
  Content,
  Search,
  GridList,
  Card
} from "../components/Page";
import { getThumbURL } from "../utils";
import GET_VIDEOS from "../Queries/Video";

const Main = () => {
  const [page, setPage] = useState(1);
  const [videos, setVideos] = useState([]);
  const lastCardRef = useRef(null);
  const intersectionObserver = new IntersectionObserver((entries, observer) => {
    const lastCard = entries[0];

    if (lastCard.intersectionRatio > 0) {
      observer.unobserve(lastCard.target);
      lastCardRef.current = null;

      setTimeout(() => {
        setPage(page + 1);
      }, 100);
    }
  });
  const query = useQuery(GET_VIDEOS, {
    variables: {
      page
    }
  });

  useEffect(() => {
    if (lastCardRef.current) {
      intersectionObserver.observe(lastCardRef.current);
    }
  });

  useEffect(() => {
    if (!query.loading) {
      setVideos(videoList => [...videoList, ...query.data.videos]);
    }
  }, [query]);

  return (
    <Page>
      <Header>
        <Search placeholder=" 검색어를 입력해주세요" />
      </Header>
      <Content>
        <GridList>
          {videos.length &&
            videos.map((video, idx) =>
              idx !== videos.length - 1 ? (
                <Card
                  key={video._id}
                  thumb={getThumbURL(video.filePath)}
                  name={video.name}
                />
              ) : (
                <Card
                  key={video._id}
                  thumb={getThumbURL(video.filePath)}
                  name={video.name}
                  ref={lastCardRef}
                />
              )
            )}
        </GridList>
      </Content>
    </Page>
  );
};

export default Main;

저는 이런 식으로 작성했습니다. 우선 lastCardRef에 마지막 카드의 ref를 넣었습니다. 그리고 페이지가 렌더링 될 때마다 useEffect 훅을 이용해, lastCardRef.current의 값을 observe 해주도록 했습니다. 그 다음 intersectionObserver의 콜백 함수에서는 intersectionRatio를 이용해 해당 엘리먼트가 보이는지 체크하고, 보일 경우 lastCardRef의 current를 null로 변경하고, 기존 관측 대상을 unobserve 한뒤 페이지를 1 더해주는 방식으로 처리했습니다. 이렇게 무한 스크롤 기능을 구현해보니, 기존 viewport의 높이를 구하고 스크롤 이벤트를 줘서 무한 스크롤을 구현하는 방식보다 편리했습니다.

Serverless 배포 시 Webpack과 Babel 사용하기

2019.07.24 16:57

Serverless 배포 시 Webpack과 Babel 사용하기

최근 프로젝트에 Serverless Framework를 사용하고 있습니다. GraphQL 백엔드를 AWS Lambda로 배포하기 위해 사용하는 데, 저는 Node.js 프로젝트를 진행할 때 Webpack, Babel을 통해 ES2016을 사용해서 작업하는 편입니다. 근데 보통 Serverless를 통해 배포하는 예제들의 경우 Webpack, Babel을 이용하는 방법이 나와있지 않아서 serverless-webpack 이라는 플러그인을 통해 Serverless에서 Webpack을 사용하는 방법을 정리해보려고 합니다.

Requirements

  • 패키지 매니저로 Yarn을 사용하고 있습니다.
  • 기본적인 Serverless 환경이 구성되어 있음을 전제로합니다.

설정

우선 플러그인 사용을 위해 serverless-webpack을 devDependencies로 설치하겠습니다.

yarn add -D serverless-webpack

그 다음 serverless.yml에 플러그인을 추가해주세요.

plugins:
  - serverless-webpack

그 다음 Webpack을 설치하고 설정하겠습니다. 저는 Webpack을 통해서 ES6을 사용할거기 때문에, 해당 설정에 맞게 패키지를 설치하겠습니다. 먼저 babel을 설치하겠습니다.

yarn add -D @babel/core @babel/cli @babel/preset-env

그 다음 프로젝트 디렉토리에 .babelrc를 생성해서 babel 설정을 하겠습니다.

{
  "presets": ["@babel/preset-env"]
}

그 다음 webpack을 설치하겠습니다.

yarn add -D webpack webpack-node-externals

webpack에서 babel을 사용할 수 있게 babel-loader도 설치하겠습니다.

yarn add -D babel-loader

그 다음 webpack.config.js를 작성하겠습니다.

const slsw = require("serverless-webpack");
const nodeExternals = require("webpack-node-externals");

module.exports = {
  entry: slsw.lib.entries,
  target: "node",
  externals: [nodeExternals()],
  mode: slsw.lib.webpack.isLocal ? "development" : "production",
  module: {
    rules: [
      {
        loader: "babel-loader",
        include: __dirname,
        exclude: /node_modules/
      }
    ]
  }
};

그 다음 serverless.yml에 webpack 관련 설정을 추가하겠습니다.

custom:
  webpack:
    webpackConfig: ./webpack.config.js
    includeModules: true

그 다음 deploy 해보시면 제대로 작동하는 것을 확인하실 수 있습니다.

React Native 앱에 Splash 스크린 추가

2019.07.02 20:47

React Native 앱에 Splash 스크린 추가

개요

많은 어플리케이션에서는 앱이 실행될 때 Splash 스크린을 띄워, 미리 데이터를 가져오거나 백그라운드 작업을 처리한 뒤 앱을 실행하는 경우가 많습니다. 오늘은 react-native-splash-screen 라이브러리를 통해 Splash 스크린을 구현하겠습니다.

개발 환경

  • React 버전 16.8.3, React Native 버전 0.59.9 버전을 사용하고 있습니다.
  • 패키지 매니저로 Yarn을 사용하고 있습니다.

라이브러리 설치

우선 react-native-splash-screen을 설치하겠습니다.

yarn add react-native-splash-screen
react-native link react-native-splash-screen

Splash 이미지 만들기

Splash 스크린을 추가하기 전 Splash 스크린에 보여줄 이미지가 있어야겠죠? 우선 generator-rn-toolbox를 설치하겠습니다.

npm install -g yo generator-rn-toolbox

Yarn의 경우 yeoman을 설치하는데 일부 오류가 있어 npm을 사용해주세요. 그 다음 이미지를 만들기 위한 imagemagick을 설치해야합니다. https://imagemagick.org/script/download.php 에서 설치해주세요. 그 다음 스플래시 이미지를 만들겠습니다. 우선 스플래시 이미지로 사용할 이미지는 2208x2208 사이즈의 psd 파일로 준비해주세요. 그 다음 generator-rn-toolbox를 통해 안드로이드 스플래시 스크린을 만들겠습니다.

yo rn-toolbox:assets --splash [스플래시 파일 경로] --android

이제 스플래시 스크린 설정을 하겠습니다.

라이브러리 사용

제가 Windows 환경에서 개발 중인 관계로 Android, RN 부분 설정만 작성하도록 하겠습니다. 우선 React Native 내부의 Android 폴더를 Android Studio에서 열어주세요. Android Studio에서 작업하는 것이 더 편리합니다. 우선 MainActivity.java를 수정하겠습니다.

...
import com.facebook.react.ReactActivity;
import org.devio.rn.splashscreen.SplashScreen;
...
public class MainActivity extends ReactActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        SplashScreen.show(this);
        super.onCreate(savedInstanceState);
    }
}

우선 org.devio.rn.splashscreen.SplashScreen을 import 하신 다음 Android Studio, IntelliJ IDEA 기준 Ctrl + O를 누르시면 Override 할 수 있는 메소드를 확인하실 수 있습니다. 거기서 ReactActivity의 onCreate를 Override 하시고 SplashScreen.show(this); 를 추가해주세요.
그리고 res/layout/launch_screen.xml 을 생성하신 뒤 아래와 같이 적어주세요.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/launch_screen" android:scaleType="centerCrop" />
</RelativeLayout>

이제 RN에서 설정을 하겠습니다.

import React from 'react';
import { createBottomTabNavigator, createAppContainer } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import Home from './pages/Home';
import Meal from './pages/Meal';
import Document from './pages/Document';
import Schedule from './pages/Schedule';
import Settings from './pages/Settings';

const Navigator = createBottomTabNavigator(
  {
    Home,
    Meal,
    Document,
    Schedule,
    Settings,
  },
  {
    defaultNavigationOptions: ({ navigation }) => ({
      tabBarIcon: ({ focused, horizontal, tintColor }) => {
        const { routeName } = navigation.state;
        const IconComponent = Ionicons;
        let iconName;

        switch (routeName) {
          case 'Home':
            iconName = `ios-home`;
            break;
          case 'Meal':
            iconName = 'ios-restaurant';
            break;
          case 'Document':
            iconName = 'ios-document';
            break;
          case 'Schedule':
            iconName = 'ios-calendar';
            break;
          case 'Settings':
            iconName = 'ios-settings';
            break;
          default:
            break;
        }

        return <IconComponent name={iconName} size={25} color={tintColor} />;
      },
    }),
    tabBarOptions: {
      activeTintColor: '#007ac1',
    },
  }
);

export default createAppContainer(Navigator);

기존 제 프로젝트의 App.js 코드입니다. React Navigation을 이용하기 때문에 createAppContainer를 export 하고 있습니다. 해당 코드를 수정하겠습니다.

import React, { useEffect } from 'react';
import { createBottomTabNavigator, createAppContainer } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import SplashScreen from 'react-native-splash-screen';
import Home from './pages/Home';
import Meal from './pages/Meal';
import Document from './pages/Document';
import Schedule from './pages/Schedule';
import Settings from './pages/Settings';

const Navigator = createBottomTabNavigator(
  {
    Home,
    Meal,
    Document,
    Schedule,
    Settings,
  },
  {
    defaultNavigationOptions: ({ navigation }) => ({
      tabBarIcon: ({ focused, horizontal, tintColor }) => {
        const { routeName } = navigation.state;
        const IconComponent = Ionicons;
        let iconName;

        switch (routeName) {
          case 'Home':
            iconName = `ios-home`;
            break;
          case 'Meal':
            iconName = 'ios-restaurant';
            break;
          case 'Document':
            iconName = 'ios-document';
            break;
          case 'Schedule':
            iconName = 'ios-calendar';
            break;
          case 'Settings':
            iconName = 'ios-settings';
            break;
          default:
            break;
        }

        return <IconComponent name={iconName} size={25} color={tintColor} />;
      },
    }),
    tabBarOptions: {
      activeTintColor: '#007ac1',
    },
  }
);

const AppContainer = createAppContainer(Navigator);

const App = () => {
  useEffect(() => {
    setTimeout(() => {
      SplashScreen.hide();
    }, 1000);
  }, []);

  return <AppContainer />;
};

export default App;

저는 이렇게 수정했습니다. Pure Functional Component를 사용하기 때문에 useEffect를 통해 처음 실행 시 1초 뒤 SplashScreen을 hide하는 코드를 작성했습니다. 저걸 네트워크 관련 코드로 처리해도 괜찮겠죠?

오늘은 이렇게 react-native-splash-screen을 사용하는법을 알아봤습니다.
읽어주셔서 감사합니다.

React Native 에서 Firebase Cloud Messaging 이용하기

2019.06.30 22:12

React Native 에서 Firebase Cloud Messaging 이용하기

개요

저는 현재 Kotlin으로 작성된 안드로이드 네이티브 앱을 React Native로 포팅하고 있습니다. 기존 어플리케이션에서는 점심 시간이 되면 푸시 알림을 통해 당일 급식 식단을 알려주는 기능이 있었는데, 해당 기능을 React Native로 포팅해야 했습니다. 해당 기능을 구현하면서 구현 방법을 블로그에 올려보면 좋을 것 같아, 해당 방법을 자세하게 알려드리려고 합니다.

주의사항

  • 저는 이미 구현되어 있는 FCM 서버를 이용하기 때문에, 해당 방법은 게시글에 포함되어 있지 않습니다.
  • Firebase 프로젝트 생성 또한 게시글에는 포함되어 있지 않습니다.

개발 환경

  • React 버전 16.8.3, React Native 버전 0.59.9 버전입니다.
  • 패키지 매니저로 Yarn을 사용하고 있습니다.

React-Native-Firebase 설치

우선 React Native에서 Firebase 관련 기능을 이용하기 위해 React-Native-Firebase를 설치하고, Link 하겠습니다.

yarn add react-native-firebase
react-native link react-native-firebase

그 다음 Android 어플리케이션에 Firebase 관련 코드를 추가해줘야 합니다.
우선 /android/app/src/java/~package_name/MainApplication.java를 열어주신 다음, 위 import가 있는 부분에 아래 코드를 추가해주세요.

import io.invertase.firebase.RNFirebasePackage;
import io.invertase.firebase.messaging.RNFirebaseMessagingPackage;
import io.invertase.firebase.notifications.RNFirebaseNotificationsPackage;

그리고 패키지 목록을 수정해줘야 합니다.

return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
          ...
          new RNFirebasePackage(),
          new RNFirebaseMessagingPackage(),
          new RNFirebaseNotificationsPackage()
      );

return Arrays.asList에 위와 같이 Firebase 관련 패키지를 추가해주세요. 그 다음 프로젝트 수준 build.gradle에 gms 관련 부분을 추가해주세요. 아래와 같이 추가해주시면 됩니다.

buildscript {
  // ...
  dependencies {
    // ...
    // Add the following line:
    classpath 'com.google.gms:google-services:4.2.0'  // Google Services plugin
  }
}

allprojects {
  // ...
  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
    // ...
  }
}

그리고 앱 수준 build.gradle에도 추가해주세요. 아래와 같이 추가해주시면 됩니다.

apply plugin: "com.android.application"

import com.android.build.OutputFile

...

android {
    ...
}

dependencies {
    ...
    implementation "com.google.firebase:firebase-core:16.0.8"
    implementation "com.google.firebase:firebase-messaging:18.0.0"
    implementation 'me.leolin:ShortcutBadger:1.1.21@aar'
}

...

apply plugin: 'com.google.gms.google-services'

그 다음 AndroidManifest.xml의 application 태그 안에 아래와 같이 추가해주세요.

<service android:name="io.invertase.firebase.messaging.RNFirebaseMessagingService">
  <intent-filter>
    <action android:name="com.google.firebase.MESSAGING_EVENT" />
  </intent-filter>
</service>
<service android:name="io.invertase.firebase.messaging.RNFirebaseBackgroundMessagingService" />

Firebase 설정

이제 Firebase 연동을 위해 Firebase Console에서 설정이 필요한 부분을 설정하겠습니다. 우선 Firebase Console에서 푸시 알림을 추가할 프로젝트에 들어가주세요.

이런식으로 들어오셨다면, 앱을 추가할 플랫폼을 클릭해주세요. 전 안드로이드 앱을 추가하겠습니다.

그 다음 Android 패키지 이름을 입력해주세요. Android 패키지 이름은 /android/build.gradle 파일의 applicationId 항목에 적혀 있습니다. 적어주셨다면 앱 등록 버튼을 눌러주세요.

등록에 성공하셨다면 위 사진과 같은 페이지를 보실 수 있습니다. google-services.json 파일을 다운받아 /android/app 폴더에 넣어주세요.

AndroidX를 사용하시는 경우

이 카테고리는 AndroidX 라이브러리를 따로 사용하시는 분이 아니라면 넘어가셔도 괜찮습니다. 저는 현재 프로젝트에서 AndroidX 라이브러리를 이용하고 있어 오류가 발생했습니다.

error: cannot find symbol
import android.support.v4.app.NotificationManagerCompat;

위와 같은 오류가 발생한다면 AndroidX 라이브러리로 인한 오류입니다. 이미 프로젝트에서 AndroidX를 사용하신 경우 아래 명령어를 실행하시면 오류가 해결됩니다.

yarn add jetifier
npx jetify

테스트

react-native run-android

위 명령어를 통해 어플리케이션을 실행했을때 오류가 없다면 연동 성공입니다!

Electron 5 업데이트 이후 window.require is not a function 오류 발생 시 해결 방법

2019.04.27 13:09

Electron 5 업데이트 이후 window.require is not a function 오류 발생 시 해결 방법

4월 25일, Electron 5.0.0 업데이트가 있었습니다. 최신 버전의 출시는 항상 좋은 일이지만, 메이저 버전 업데이트의 경우 많은 오류를 동반하는 경우가 많습니다. 특히 deprecated 예정 API를 사용하는 경우 해당 오류를 피할 수 없는 경우가 많습니다. 저 또한 개발 중이던 프로젝트의 Electron 버전을 5로 업데이트하자 첫 페이지부터 오류가 저를 반겼습니다.

처음에는 window.require is not a function 오류를 구글에 검색해서 해결 방법을 찾아보려 했습니다. 제 개발 환경이 CRA와 Electron인 만큼 react를 붙여 검색해보기도 했는데, 방법을 찾기는 힘들었습니다. 그래서 electron 5를 붙여 검색해보니 한 문서를 찾을 수 있었습니다. 

많은 라이브러리에서는 메이저 업데이트 전, 큰 변경사항을 미리 문서로 고지하고 있고, Electron 역시 동일합니다. 많은 오류를 구글링과 stackoverflow를 통해 해결할 수 있다 보니 가장 중요한 사실을 잊었었습니다.

제가 봤을 때 이번 오류의 원인으로 보이는 부분은 이 부분이었습니다. 원래는 BrowserWindow를 생성할 때, webPreferences의 nodeIntegration의 값을 별도로 부여해주지 않아도 기본 값이 true이기 때문에, 정상적으로 window.require API를 이용할 수 있었지만, 5 버전 부터는 기본 값으로 false가 들어가기 때문에 사용할 수 없는 것으로 보였고, 바로 코드를 수정했습니다. 그래서 코드를 수정했습니다.

 

// 수정 전 코드
win = new BrowserWindow({ width: 1080, height: 1920, kiosk: true, 'fullscreen': true, 'frame': false });
// 수정 후 코드
win = new BrowserWindow({ width: 1080, height: 1920, kiosk: true, 'fullscreen': true, 'frame': false, webPreferences: { nodeIntegration: true } }); 

 

코드를 수정하고 나니 문제없이 작동하는 것을 볼 수 있습니다. 역시 공식 문서를 잘 읽어봐야겠네요.

짧게 정리하자면, BrowserWindow의 webPreferences의 nodeIntegration을 true로 주시면 해결되는 문제입니다.

 

 

Default FirebaseApp is not initialized 오류 발생시

2019.04.07 12:57

Default FirebaseApp is not initialized 오류 발생 시

안드로이드에서 Firebase를 사용할 때, 종종 발생하는 오류입니다.

오류 메세지 자체에서는 FirebaseApp.initializeApp(context)를 호출하라고 보내는데, 이렇게 해도 오류가 해결되지 않는 경우가 많습니다.

해당 상황은 Firebase의 초기 설정에서 놓친 부분이 있는 경우가 대다수입니다.

일단 제 프로젝트에서 초기 설정을 살펴보면서 문제를 해결해보도록 하겠습니다.

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext.kotlin_version = '1.3.11'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.gms:google-services:4.1.0'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

우선 프로젝트 수준의 build.gradle 파일을 살펴보겠습니다.

Android Studio에서는 google-services의 최신 버전인 4.2.0 버전을 사용하라고 권고합니다.

해당 사항을 수정하겠습니다.

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.gms.google-services'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.kentastudio.izonestream"
        minSdkVersion 21
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support:design:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:cardview-v7:27.1.1'
    implementation 'com.android.support:support-v4:27.1.1'
    implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'com.google.android.exoplayer:exoplayer:2.8.4'
    implementation 'com.github.bumptech.glide:glide:4.9.0'
    implementation 'com.google.firebase:firebase-messaging:17.3.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    kapt 'com.github.bumptech.glide:compiler:4.9.0'
}

그 다음 어플리케이션 수준의 build.grade 파일입니다.

보통 문서를 찾아보면 gms.google-services 부분을 파일의 끝에 넣으라는 권고가 많아 해당 부분과

firebase-messaging 라이브러리의 버전을 바꾸도록 하겠습니다.

알림이 제대로 작동하는 것을 확인할 수 있습니다.

보통 Default FirebaseApp is not initialized 오류는 설정을 다시 한번 확인해보면 간단하게 해결할 수 있습니다.

[Expo] 앱 실행 시 Network response timed out 오류 발생시

2019.03.10 20:35

[Expo] 앱 실행시 Network response timed out 오류 발생 시


저는 React Native 개발을 할 때 Expo를 자주 사용하는 편입니다.

개발 시 정말 편리한 점이 많고, Expo 앱을 통해서 실행할 때 케이블이 없어도 가능해서 좋아합니다.

하지만 가끔식 LAN으로 실행 시 Network Response timed out 오류가 발생하는 경우가 있습니다.


이런식으로 오류가 발생하게 되는데, 저는 방화벽 설정을 통해 해결했습니다.

우선 Windows Defender 방화벽에서 고급 설정으로 가주세요.


그럼 이런식으로 현재 방화벽 설정을 확인할 수 있습니다.

여기서 인바운드 규칙에 포트 19000, 19001을 추가해주시면 정상적으로 작동하는 걸 확인하실 수 있습니다.